package com.yanqu.road.server.manager.activity.tongbing.activity;

import com.alibaba.fastjson.JSON;
import com.yanqu.road.entity.activity.ActivityInfo;
import com.yanqu.road.entity.activity.camp.CampUnion;
import com.yanqu.road.entity.activity.tongbing.CrossTongBingRankSort;
import com.yanqu.road.entity.activity.tongbing.config.*;
import com.yanqu.road.entity.activity.tongbing.data.*;
import com.yanqu.road.entity.activity.tongbing.join.CrossTongBingUnionJoin;
import com.yanqu.road.entity.activity.tongbing.join.CrossTongBingUnionJoinMember;
import com.yanqu.road.entity.config.goods.GoodsInfo;
import com.yanqu.road.entity.config.patrons.PatronsInfo;
import com.yanqu.road.entity.enums.*;
import com.yanqu.road.entity.log.cross.fivehero.LogFiveHeroPatronsUpdate;
import com.yanqu.road.entity.log.cross.tongbing.normal.*;
import com.yanqu.road.entity.player.UserBaseInfo;
import com.yanqu.road.entity.scene.ScenePlayerInfo;
import com.yanqu.road.entity.scene.UserScene;
import com.yanqu.road.entity.union.UnionBaseInfo;
import com.yanqu.road.logic.activity.tongbing.logic.TongBingMapLogic;
import com.yanqu.road.logic.bussiness.activity.TongBingActivityBusiness;
import com.yanqu.road.logic.config.GameConfig;
import com.yanqu.road.logic.helper.UnionActivityGroupHelper;
import com.yanqu.road.logic.pb.CrossTongBingPb;
import com.yanqu.road.logic.pb.PlayerBasePb;
import com.yanqu.road.logic.pb.UnionBasePb;
import com.yanqu.road.message.YanQuMessage;
import com.yanqu.road.pb.activity.TonBingProto;
import com.yanqu.road.pb.activity.TongBingProto;
import com.yanqu.road.pb.cross.activity.CrossActivityProto;
import com.yanqu.road.pb.rank.RankProto;
import com.yanqu.road.pb.worldmessage.WorldMessageProto;
import com.yanqu.road.server.logic.MessageHelper;
import com.yanqu.road.server.manager.activity.ActivityMgr;
import com.yanqu.road.server.manager.activity.camp.CampApi;
import com.yanqu.road.server.manager.activity.camp.eCampActivityType;
import com.yanqu.road.server.manager.activity.tonbing.activity.Camp;
import com.yanqu.road.server.manager.activity.tongbing.logic.CrossTongBingChatLogic;
import com.yanqu.road.server.manager.activity.tongbing.task.BaseCrossTongBingTask;
import com.yanqu.road.server.manager.activity.tongbing.task.impl.*;
import com.yanqu.road.server.manager.config.GoodsMgr;
import com.yanqu.road.server.manager.config.PatronsMgr;
import com.yanqu.road.server.manager.log.CrossAutoLogMgr;
import com.yanqu.road.server.manager.scene.CrossSceneMgr;
import com.yanqu.road.server.manager.union.activitygroup.Cross2UnionActivityGroupMgr;
import com.yanqu.road.server.manager.user.CrossUserMgr;
import com.yanqu.road.server.protocol.*;
import com.yanqu.road.utils.RandomHelper;
import com.yanqu.road.utils.YanQuMessageUtils;
import com.yanqu.road.utils.date.DateHelper;
import com.yanqu.road.utils.property.Property;
import com.yanqu.road.utils.property.PropertyHelper;
import com.yanqu.road.utils.string.StringUtils;
import org.apache.mina.util.ConcurrentHashSet;

import java.math.BigDecimal;
import java.math.BigInteger;
import java.math.RoundingMode;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.PriorityBlockingQueue;
import java.util.stream.Collectors;

public class CrossTongBingActivity extends CrossTongBingBaseActivity implements CampApi {

    private RandomHelper randomHelper = new RandomHelper();

    private int groupId;

    /**
     * 活动总数据
     */
    private CrossTongBingData crossTongBingData;

    /**
     * 商会数据
     */
    private Map<String, CrossTongBingUnionJoin> unionJoinMap = new ConcurrentHashMap<>();

    /**
     * 玩家数据
     */
    private Map<Long, CrossTongBingUserData> userDataMap = new ConcurrentHashMap<>();

    /**
     * 阵营数据
     */
    private Map<Integer, CrossTongBingCampData> campDataMap = new ConcurrentHashMap<>();

    /**
     * 城池战报
     */
    private List<CrossTongBingCityWarReport> cityWarReportList = new ArrayList();

    /**
     * 个人战报
     */
    private List<CrossTongBingUserWarReport> userWarReportsList = new ArrayList<>();

    // ============================== 缓存数据 ==============================

    // 待入库战报
    private List<CrossTongBingUserWarReport> needInsertUserReportList = new ArrayList();
    private List<CrossTongBingCityWarReport> needInsertCityReportList = new ArrayList();

    // 聊天
    private CrossTongBingChatLogic chatLogic;

    // 榜单
    private CrossTongBingRankListModel userRank = new CrossTongBingRankListModel(); // 个人榜
    private Map<Integer, CrossTongBingRankListModel> campUserRankMap = new ConcurrentHashMap(); // 阵营内个人榜
    private Map<Integer, CrossTongBingRankListModel> campUnionRankMap = new ConcurrentHashMap<>(); // 阵营内商会榜

    // 城主职位
    private Map<Long, List<CrossTongBingUserData>> needSyncPosUserData = new ConcurrentHashMap<>();

    // 需要同步的队伍
    private final Set<String> needSyncTeam = new ConcurrentHashSet<>();

    // 需要同步的城池
    private final Set<Integer> needSyncCity = new ConcurrentHashSet<>();

    // ============================== 构造方法 ==============================

    public CrossTongBingActivity(ActivityInfo activityInfo, int groupId) {
        super(activityInfo);
        this.groupId = groupId;

        // 【读取数据】

        crossTongBingData = TongBingActivityBusiness.getCrossTongBingData(activityInfo.getActivityId(), groupId);
        if (crossTongBingData == null) {
            crossTongBingData = new CrossTongBingData();
            crossTongBingData.setActivityId(activityInfo.getActivityId());
            crossTongBingData.setGroupId(groupId);
            crossTongBingData.setLockUnion(false);
            crossTongBingData.setProcess2WinUserId(0L);
            crossTongBingData.setRefreshCityEventTime(config.getStage1TimeList().get(0).get(0)); // 初始化为一阶段开始时间
            crossTongBingData.setApplyCityEventTime(config.getStage1TimeList().get(0).get(0)); // 初始化为一阶段开始时间
            crossTongBingData.setCityEventMap(createInitCityEvent());
            crossTongBingData.setCampBuffMap(new ConcurrentHashMap<>());
            crossTongBingData.setNpcEventAddTime(config.getStage1TimeList().get(0).get(0) - config.getNpcEventInterval()); // 这样一阶段开始时才能生成一个
            crossTongBingData.setCityMap(createCity());
            crossTongBingData.setInsertOption();
        }

        unionJoinMap = TongBingActivityBusiness.getCrossTongBingUnionJoinMap(activityInfo.getActivityId(), groupId);

        userDataMap = TongBingActivityBusiness.getCrossTongBingUserDataMap(activityInfo.getActivityId(), groupId);

        cityWarReportList = TongBingActivityBusiness.getCityReportList(activityInfo.getActivityId(), groupId);

        userWarReportsList = TongBingActivityBusiness.getUserReportList(activityInfo.getActivityId(), groupId);

        campDataMap = TongBingActivityBusiness.getCrossTongBingCampDataMap(activityInfo.getActivityId(), groupId);

        // 【其他初始化】

        initUserRedPacketNumMap(); // 初始化红包发送数量
        initNeedSyncPositionUser();
        chatLogic = new CrossTongBingChatLogic(activityInfo, groupId);
        initAllRankList();

        // 启动线程
        initTask();
        thread.start();
    }

    // ============================== 数据生成获取 ==============================

    @Override
    public void initUserData(long userId) {
        // 生成userData
        if (!userDataMap.containsKey(userId)) {
            synchronized (lock) {
                if (!userDataMap.containsKey(userId)) {
                    CrossTongBingUserData userData = CrossTongBingUserData.initCrossTongBingUserData(config.getActivityInfo().getActivityId(), groupId, userId);

                    // 初始化兵营部分数据
                    CrossTongBingBarracks barracks = userData.getBarracks();
                    barracks.setHospitalSize(config.getWoundedSoldierMaxNum());
                    barracks.setRescueSpeed(config.getWoundedRescueTime());
                    barracks.setRescueCount(config.getWoundedRescueCount());
                    userData.setUpdateOption();

                    // 加到map
                    userDataMap.put(userId, userData);
                }
            }
        }
    }

    @Override
    public List<CampUnion> resort(List<CampUnion> campUnions) {
        return campUnions.stream().filter(p -> p instanceof CrossTongBingUnionJoin)
                .map(p -> (CrossTongBingUnionJoin) p).sorted((o1, o2) -> {
                    if (o1.getCompressedAbility().compareTo(o2.getCompressedAbility()) == 0) { // 压缩战力一致，比较oldRank
                        return Integer.compare(o1.getOldRank(), o2.getOldRank());
                    }
                    return o2.getCompressedAbility().compareTo(o1.getCompressedAbility());
                }).collect(Collectors.toList());
    }

    @Override
    public void setUnionJoinCamp(CampUnion campUnion, int campId) {
        if (campUnion == null) {
            return;
        }

        campUnion.setCampId(campId);

//        CrossTongBingUnionJoin union = (CrossTongBingUnionJoin) campUnion;
//        Map<Long, CrossTongBingUnionJoinMember> memberMap = union.getMemberMap();
//        if (memberMap == null || memberMap.isEmpty()) {
//            logger.error("活动:{} 分组:{} 商会:{} 分阵营时，商会成员为空", config.getActivityInfo().getActivityId(), groupId, union.getUnionUid());
//        }
//        for (CrossTongBingUnionJoinMember member : union.getMemberMap().values()) {
//            CrossTongBingUserData userData = getUserData(member.getUserId());
//            if (userData != null) {
//                userData.setCampId(campId);
//                userData.setUnionUid(union.getUnionUid());
//            }
//        }
    }

    /**
     * 初始化需要同步的会获得银两的职位的玩家
     */
    private void initNeedSyncPositionUser() {
        Map<Integer, TongBingTitleConfig> titleConfigMap = config.getTitleConfigMap();
        for (CrossTongBingUserData userData : userDataMap.values()) {
            if (userData.getPositionId() != eTongBingPosition.None.getValue()) {
                if (titleConfigMap.get(userData.getPositionId()).getType() == 1) {
                    addNeedSyncPosUser(userData);
                }
            }
        }
    }

    /**
     * 初始化阵营数据
     */
    private void initCampUserData() {

        logger.info("initCampUserData activityId {}.", config.getActivityInfo().getActivityId());

        // 阵营数据生成
        for (int campId = 1; campId <= config.getCampNum(); campId++) {
            if (!campDataMap.containsKey(campId)) {
                CrossTongBingCampData campData = new CrossTongBingCampData();
                campData.setActivityId(config.getActivityInfo().getActivityId());
                campData.setGroupId(groupId);
                campData.setCampId(campId);
                campData.setInsertOption();
                campDataMap.put(campId, campData);
            } else {
                // 已有数据说明有问题...
                logger.error("tong bing initCampUserData, repeated create data? activityId {}, campId {}.", config.getActivityInfo().getActivityId(), campId);
                CrossTongBingCampData campData = campDataMap.get(campId);
                campData.setCollectionMap(new ConcurrentHashMap<>());
                campData.setRedPacketMap(new ConcurrentHashMap<>());
                campData.setRedPacketValue(0L);
                campData.setUserRedPacketMap(new ConcurrentHashMap<>());
                campDataMap.put(campId, campData);
            }
        }
    }

    //触发玩家被任命后的增益银两收成，这里只用定时器去触发，就不加锁了
    //需求是城主加成英两且获得别人扣除的英两，通过领取，增益buff的也是加银两然后领取，减益buff的玩家要去本服他的英两收益变小
    public void makePosSliver() {
        //领奖期才能任命
        long nowTime = System.currentTimeMillis();
        if (nowTime < config.getActivityInfo().getEndTime() * 1000 || nowTime > config.getActivityInfo().getEndShowTime() * 1000) {
            return;
        }
        Map<Integer, TongBingTitleConfig> titleConfigMap = config.getTitleConfigMap();
        for (CrossTongBingUserData userData : userDataMap.values()) {
            synchronized (lock) {
                if (userData.getPositionId() != eTongBingPosition.None.getValue()) {
                    TongBingTitleConfig titleConfig = titleConfigMap.get(userData.getPositionId());
                    if (titleConfig.getType() == 1) {
                        /*long lastCalTime = userData.getPosAddSilverTime();
                        long sec = (nowTime - lastCalTime) / 1000;
                        Map<Long, BigInteger> addMap = userData.getPosAddSilverMap();
                        BigInteger earnSpeed = getUserEarnSpeed(userData.getUserId());
                        BigInteger oldValue = addMap.getOrDefault(userData.getUserId(), BigInteger.ZERO);
                        BigInteger addValue = BigInteger.valueOf(sec).multiply(earnSpeed).multiply(BigInteger.valueOf(titleConfig.getEffect())).divide(BigInteger.valueOf(1000));
                        addMap.put(userData.getUserId(), oldValue.add(addValue));
                        userData.setPosAddSilverMap(addMap);
                        userData.setPosAddSilverTime(nowTime);*/
                    } else if (titleConfig.getType() == 2) {
                        long lastCalTime = userData.getPosAddSilverTime();
                        long sec = (nowTime - lastCalTime) / 1000;
                        BigInteger earnSpeed = getUserEarnSpeed(userData.getUserId());
                        BigInteger addValue = BigInteger.valueOf(sec).multiply(earnSpeed).multiply(BigInteger.valueOf(titleConfig.getEffect())).divide(BigInteger.valueOf(1000));
                        // 累计上缴银两
                        userData.setTotalPosSilver(userData.getTotalPosSilver().add(addValue));
                        //每个人分配到的不一样
                        posSliverAdd(addValue,userData.getUserId());
                        userData.setPosAddSilverTime(nowTime);
                    }
                }
            }
        }
        syncUserPositionSliver();
    }

    //按照配在百分百算钱
    private void posSliverAdd(BigInteger addValue,long userId) {
        Map<Integer, TongBingTitleConfig> titleConfigMap = config.getTitleConfigMap();
        int allAddWeight = 0;
        for (TongBingTitleConfig titleConfig : titleConfigMap.values()) {
            if (titleConfig.getType() == 1) {
                allAddWeight = allAddWeight + titleConfig.getEffect();
            }
        }
        for (CrossTongBingUserData userData : userDataMap.values()) {
            TongBingTitleConfig titleConfig = titleConfigMap.get(userData.getPositionId());
            if (titleConfig != null && titleConfig.getType() == 1) {
                Map<Long, BigInteger> addMap = userData.getPosAddSilverMap();
                BigInteger oldValue = addMap.getOrDefault(userId, BigInteger.ZERO);
                BigInteger userAdd = addValue.multiply(BigInteger.valueOf(titleConfig.getEffect())).divide(BigInteger.valueOf(allAddWeight));
                // 累计收取银两
                userData.setTotalPosSilver(userData.getTotalPosSilver().add(userAdd));
                addMap.put(userId, oldValue.add(userAdd));
                userData.setUpdateOption();
            }
        }
    }

    //添加任命完的人的
    private void addNeedSyncPosUser(CrossTongBingUserData userData) {
        synchronized (lock) {
            UserBaseInfo userBaseInfo = CrossUserMgr.getUserBaseInfo(userData.getUserId());
            if (userBaseInfo != null) {
                if (!needSyncPosUserData.containsKey(userBaseInfo.getServerId())) {
                    needSyncPosUserData.put(userBaseInfo.getServerId(), new ArrayList<>());
                }
                needSyncPosUserData.get(userBaseInfo.getServerId()).add(userData);
            }
        }
    }

    private Map<Long, List<CrossTongBingUserData>> getNeedSyncPosUserDataMap() {
        synchronized (lock) {
            if (needSyncPosUserData.size() == 0) {
                Map<Long, List<CrossTongBingUserData>> tempMap = new ConcurrentHashMap<>();
                Map<Integer, TongBingTitleConfig> titleConfigMap = config.getTitleConfigMap();
                for (CrossTongBingUserData userData : userDataMap.values()) {
                    if (userData.getPositionId() != eTongBingPosition.None.getValue()) {
                        TongBingTitleConfig titleConfig = titleConfigMap.get(userData.getPositionId());
                        if (titleConfig.getType() == 1) {
                            UserBaseInfo userBaseInfo = CrossUserMgr.getUserBaseInfo(userData.getUserId());
                            if (userBaseInfo != null) {
                                if (!tempMap.containsKey(userBaseInfo.getServerId())) {
                                    tempMap.put(userBaseInfo.getServerId(), new ArrayList<>());
                                }
                                tempMap.get(userBaseInfo.getServerId()).add(userData);
                            }
                        }
                    }
                }
                needSyncPosUserData = tempMap;
            }
            return needSyncPosUserData;
        }
    }

    //同步玩家身上的数据
    public void syncUserData(long userId, TongBingProto.TongBingUserDataSyncMsg reqMsg) {
        TongBingProto.TongBingUserDataSyncMsg.Builder respMsg = reqMsg.toBuilder();
        respMsg.setWinnerId(crossTongBingData.getProcess2WinUserId());
        sendPacket(ClientProtocol.U_TONG_BING_USER_DATA_SYNC, respMsg, userId);
    }

    //城池有了同步客户端告诉他要出图标了
    public void syncWinnerGetNotice() {
        List<Long> serverList;
        if (UnionActivityGroupHelper.needGroup(config.getActivityInfo())) {
            serverList = new ArrayList<>(Cross2UnionActivityGroupMgr.getGroupData(config.getActivityInfo().getActivityId(), groupId).getServerSet());
        } else {
            serverList = config.getActivityInfo().getServerIdList();
        }

        // 重复发也不要紧
        for (long serverId : serverList) {
            TongBingProto.CrossTongBingReqMsg.Builder reqMsg = TongBingProto.CrossTongBingReqMsg.newBuilder();
            reqMsg.setActivityId(config.getActivityInfo().getActivityId());
            MessageHelper.sendPacket(serverId, 0, YanQuMessageUtils.buildMessage(GameProtocol.S_TONG_BING_SYNC_USER_DATA, reqMsg));
        }
    }

    //同步有职位的玩家的领取奖励数信息
    public void syncUserPositionSliver() {
        Map<Long, List<CrossTongBingUserData>> needSyncUserDataMap = new ConcurrentHashMap<>(getNeedSyncPosUserDataMap());
        for (Long serverId : needSyncUserDataMap.keySet()) {
            TongBingProto.CrossTongBingPositionSliverSyncMsg.Builder syncMsg = TongBingProto.CrossTongBingPositionSliverSyncMsg.newBuilder();
            List<CrossTongBingUserData> userDataList = needSyncPosUserData.get(serverId);
            for (CrossTongBingUserData userData : userDataList) {
                TongBingProto.CrossTongBingPositionUserMsg.Builder userMsg = TongBingProto.CrossTongBingPositionUserMsg.newBuilder();
                userMsg.setUserId(userData.getUserId());
                Map<Long, BigInteger> addMap = userData.getPosAddSilverMap();
                for (long userId : addMap.keySet()) {
                    UserBaseInfo userBaseInfo = CrossUserMgr.getUserBaseInfo(userId);
                    if (userBaseInfo != null) {
                        TongBingProto.TongBingPositionSliverMsg.Builder sliverUserMsg = TongBingProto.TongBingPositionSliverMsg.newBuilder();
                        sliverUserMsg.setUserId(userId);
                        sliverUserMsg.setSliver(addMap.get(userId).toString());
                        sliverUserMsg.setPlayerBaseData(PlayerBasePb.parsePlayerBaseTempMsg(userBaseInfo));
                        userMsg.addSliverUser(sliverUserMsg);
                    }
                }
                syncMsg.addUser(userMsg);
            }
            MessageHelper.sendPacket(serverId, 0, YanQuMessageUtils.buildMessage(GameProtocol.S_CROSS_TONG_BING_POS_REWARD_SYNC, syncMsg));
        }
    }

    //领取职位产生的银两奖励
    public void getPosReward(long serverId, long userId) {
        TongBingProto.TongBingPosGetRewardRespMsg.Builder respMsg = TongBingProto.TongBingPosGetRewardRespMsg.newBuilder();
        respMsg.setRet(0);
        CrossTongBingUserData userData = getUserData(userId);
        if (!isAble(userData)) {
            respMsg.setRet(GameErrorCode.E_TONG_BING_NO_ABLE);
            sendPacket(ClientProtocol.U_TONG_BING_GET_POS_REWARD, respMsg, userId);
            return;
        }
        synchronized (lock) {
            if (userData.getPositionId() == eTongBingPosition.None.getValue()) {
                respMsg.setRet(GameErrorCode.E_TONG_BING_POS_REWARD_CAN_NOT_GET);
                sendPacket(ClientProtocol.U_TONG_BING_GET_POS_REWARD, respMsg, userId);
                return;
            }
            Map<Integer, TongBingTitleConfig> titleConfigMap = config.getTitleConfigMap();
            TongBingTitleConfig titleConfig = titleConfigMap.get(userData.getPositionId());
            if (titleConfig.getType() != 1) {
                respMsg.setRet(GameErrorCode.E_TONG_BING_POS_REWARD_CAN_NOT_GET);
                sendPacket(ClientProtocol.U_TONG_BING_GET_POS_REWARD, respMsg, userId);
                return;
            }
            Map<Long, BigInteger> rewardMap = userData.getPosAddSilverMap();
            BigInteger allValue = BigInteger.ZERO;
            for (long unLuckUserId : rewardMap.keySet()) {
                BigInteger value = rewardMap.get(unLuckUserId);
                allValue = allValue.add(value);
                rewardMap.put(unLuckUserId, BigInteger.ZERO);
                userData.setUpdateOption();
            }
            if (allValue.compareTo(BigInteger.ZERO) == 0) {
                respMsg.setRet(GameErrorCode.E_TONG_BING_NO_POS_REWARD);
                sendPacket(ClientProtocol.U_TONG_BING_GET_POS_REWARD, respMsg, userId);
                return;
            }
            userData.setPosAddSilverMap(rewardMap);
            respMsg.setReward(GameConfig.GAME_MONEY_SILVER + "=" + allValue.toString());
        }
        MessageHelper.sendPacket(serverId, userId, YanQuMessageUtils.buildMessage(GameProtocol.S_CROSS_TONG_BING_GET_POS_REWARD, respMsg));
    }

    /**
     * 触发城池的采集收益 只触发对应阵营的
     * 玩家点击要领取的时候触发下，被打下来触发下，buff失效的时候触发下
     */
    private void makeAllCityCollect(int campId, long dealTime) {
        // 阵营空、未生成数据，不处理
        if (campId == 0 || !campDataMap.containsKey(campId)) {
            return;
        }

        List<Integer> needDealCity = new ArrayList<>();
        long nowTime = dealTime;
        Map<Integer, TongBingCityConfig> cityConfigMap = config.getCityConfigMap();
        Map<Integer, CrossTongBingCity> cityMap = crossTongBingData.getCityMap();
        CrossTongBingCampData campData = campDataMap.get(campId);

        synchronized (lock) {
            // 获取有收益的城池
            for (CrossTongBingCity city : cityMap.values()) {
                if (city.getCampId() != campId) {
                    continue;
                }
                TongBingCityConfig cityConfig = cityConfigMap.get(city.getCityId());
                TongBingCityTypeConfig cityTypeConfig = config.getCityTypeConfigMap().get(cityConfig.getType());
                // 每小时产生一次收益
                if (cityTypeConfig.getFoodProduction() > 0 && nowTime - city.getCollectionTime() >= DateHelper.HOUR_MILLIONS) {
                    needDealCity.add(city.getCityId());
                }
            }
            // 城池加采集奖励
            Map<Long, Map<Integer, Long>> collectionMap = campData.getCollectionMap();
            for (int cityId : needDealCity) {
                // 计算产量
                long productValue = getCityFoodProduct(cityId, nowTime);
                // 给所有玩家加
                addAllUserCityProductReward(campId, cityId, productValue, collectionMap);
            }
            if (needDealCity.size() > 0) {
                campData.setUpdateOption();
            }
        }

        if (!needDealCity.isEmpty()) {
            // 红点
            for (Map.Entry<Long, Map<Integer, Long>> entry : campData.getCollectionMap().entrySet()) {
                long userId = entry.getKey();
                long totalValue = 0;
                for (Long value : entry.getValue().values()) {
                    totalValue += value;
                }
                if (userId > 0 && totalValue > config.getRedDotCollectCount()) {
                    addRedDot(getUserData(userId), eTongBingRedDotType.Type10_GetCollect);
                }
            }
        }
    }

    /**
     * buff后产量
     */
    private long getCityFoodProductBuffPer(int cityId) {
        Map<Integer, CrossTongBingCity> cityMap = crossTongBingData.getCityMap();
        CrossTongBingCity cityData = cityMap.get(cityId);
        TongBingCityConfig cityConfig = config.getCityConfigMap().get(cityId);
        TongBingCityTypeConfig cityTypeConfig = config.getCityTypeConfigMap().get(cityConfig.getType());
        Integer buffAddition = getBuffAddition(cityData.getCampId(), eTongBingBuffType.Type5_CityFoodProduce).get(0);
        BigDecimal production = BigDecimal.valueOf(cityTypeConfig.getFoodProduction())
                .multiply(BigDecimal.valueOf(1000 + buffAddition)).divide(BigDecimal.valueOf(1000), 0, BigDecimal.ROUND_DOWN);
        return production.longValue();
    }

    // 城池指定时间的采集产量
    private long getCityFoodProduct(int cityId, Long nowTime) {
        Map<Integer, CrossTongBingCity> cityMap = crossTongBingData.getCityMap();
        CrossTongBingCity cityData = cityMap.get(cityId);
        // 收益多少次
        long times = (nowTime - cityData.getCollectionTime()) / DateHelper.HOUR_MILLIONS;
        cityData.setCollectionTime(cityData.getCollectionTime() + times * DateHelper.HOUR_MILLIONS);
        crossTongBingData.setUpdateOption();

        BigInteger allValue = BigInteger.valueOf(times).multiply(BigInteger.valueOf(getCityFoodProductBuffPer(cityId)));
        return allValue.longValue();
    }

    /**
     * 给同阵营所有玩家加采集奖励
     */
    private void addAllUserCityProductReward(int campId, int cityId, long productValue, Map<Long, Map<Integer, Long>> userCityValueMap) {
        for (CrossTongBingUserData userData : userDataMap.values()) {
            if (!isAble(userData) || userData.getCampId() != campId) {
                continue;
            }

            if (!userCityValueMap.containsKey(userData.getUserId())) {
                userCityValueMap.put(userData.getUserId(), new ConcurrentHashMap<>());
            }
            Map<Integer, Long> cityValueMap = userCityValueMap.get(userData.getUserId());

            Long oldValue = cityValueMap.getOrDefault(cityId, 0L);
            cityValueMap.put(cityId, oldValue + productValue);
        }
    }

    /**
     * 城池采集界面信息
     */
    public void cityFoodInfo(long userId) {
        CrossTongBingUserData userData = getUserData(userId);
        if (!isAble(userData)) {
            // 没资格的不可能打开采集界面
            return;
        }

        int campId = userData.getCampId();

        // 重新计算采集
        makeAllCityCollect(campId, System.currentTimeMillis());

        // 协议
        TongBingProto.TongBingCityFoodRespMsg.Builder respMsg = TongBingProto.TongBingCityFoodRespMsg.newBuilder();
        respMsg.setRet(0);

        Set<Integer> hadCitySet = new HashSet<>();

        CrossTongBingCampData campData = campDataMap.get(campId);
        if (campData != null) {
            // 有收益的城池
            Map<Integer, Long> cityValueMap = campData.getCollectionMap().getOrDefault(userId, new ConcurrentHashMap<>());
            for (Map.Entry<Integer, Long> entry : cityValueMap.entrySet()) {
                int cityId = entry.getKey();
                long value = entry.getValue();

                if (value <= 0) {
                    continue; // 没收益
                }

                TongBingProto.TongBingCityFoodMsg.Builder tempMsg = TongBingProto.TongBingCityFoodMsg.newBuilder();
                tempMsg.setCityId(cityId);
                tempMsg.setFood(value);
                tempMsg.setFoodProduction(getCityFoodProductBuffPer(cityId)); // 产出速度
                respMsg.addFoods(tempMsg);

                hadCitySet.add(cityId);
            }
        }

        // 无收益但占领的城池
        for (CrossTongBingCity city : getCrossTongBingData().getCityMap().values()) {
            if (hadCitySet.contains(city.getCityId()) || city.getCampId() != campId) {
                continue;
            }
            TongBingProto.TongBingCityFoodMsg.Builder tempMsg = TongBingProto.TongBingCityFoodMsg.newBuilder();
            tempMsg.setCityId(city.getCityId());
            tempMsg.setFood(0);
            tempMsg.setFoodProduction(getCityFoodProductBuffPer(city.getCityId()));
            respMsg.addFoods(tempMsg);
        }

        sendPacket(ClientProtocol.U_TONG_BING_CITY_FOOD, respMsg, userId);
    }

    /**
     * 城池采集一键收获
     */
    public void cityFoodCollect(long userId) {

        TongBingProto.CrossTongBingCityFoodCollectRespMsg.Builder builder = TongBingProto.CrossTongBingCityFoodCollectRespMsg.newBuilder();
        builder.setRet(0);

        CrossTongBingUserData userData = getUserData(userId);

        if (!isAble(userData)) {
            builder.setRet(GameErrorCode.E_TONG_BING_NO_ABLE);
            sendPacket(ClientProtocol.U_TONG_BING_CITY_FOOD_COLLECT, builder, userId);
            return; // 无资格不可能进去点一键收获
        }

        // 重新计算收益
        makeAllCityCollect(userData.getCampId(), System.currentTimeMillis());

        // 统计总奖励
        long userAllValue = 0L;
        CrossTongBingCampData campData = campDataMap.get(userData.getCampId());
        if (campData != null) {
            synchronized (lock) {
                Map<Long, Map<Integer, Long>> collectionMap = campData.getCollectionMap();
                Map<Integer, Long> cityValueMap = collectionMap.getOrDefault(userId, new ConcurrentHashMap<>());
                for (Long value : cityValueMap.values()) {
                    userAllValue += value;
                }
                collectionMap.put(userId, new ConcurrentHashMap<>()); // 清空
                campData.setUpdateOption();
            }
        }

        if (userAllValue <= 0) {
            builder.setRet(GameErrorCode.E_TONG_BING_CITY_FOOD_COLLECT_NO_VALUE);
            sendPacket(ClientProtocol.U_TONG_BING_CITY_FOOD_COLLECT, builder, userId);
            return; // 没奖励
        }

        String reward = config.getZiZhongItem() + "=" + userAllValue;
        builder.setReward(reward);
        sendPacket(GameProtocol.S_CROSS_TONG_BING_CITY_FOOD_COLLECT, builder, userId);
    }

    private CrossTongBingRedPacket oneRedPacket(int campId, long userId, int goodId) {
        CrossTongBingCampData campData = campDataMap.get(campId);
        Map<Long, CrossTongBingRedPacket> redPacketMap = campData.getRedPacketMap();

        CrossTongBingRedPacket redPacket;
        synchronized (lock) {
            // 生成红包
            redPacket = new CrossTongBingRedPacket();
            redPacket.setUserId(userId);
            redPacket.setGoodId(goodId);
            redPacket.setTime(System.currentTimeMillis());
            redPacket.setRedPacketNo(redPacketMap.size() + 1);
            // 加map
            redPacketMap.put(redPacket.getRedPacketNo(), redPacket);
            campData.setUpdateOption();
            // 加统计数量
            addUserRedPacketCount(redPacket, campData);
        }

        return redPacket;
    }

    /**
     * 使用道具后给阵营加红包
     */
    public void addRedPacket(long userId, TongBingProto.CrossTongBingRedPacketAddReqMsg reqMsg) {
        CrossTongBingUserData userData = getUserData(userId);
        if (!isAble(userData)) {
            logger.error("tong bing addRedPacket fail, no able! activityId {}, userId {}.", config.getActivityInfo().getActivityId(), userId);
            return;
        }

        int campId = userData.getCampId();
        CrossTongBingCampData campData = campDataMap.get(campId);
        if (campData == null) {
            logger.error("tong bing addRedPacket fail, no camp data! activityId {}, userId {}.", config.getActivityInfo().getActivityId(), userId);
            return;
        }

        List<CrossTongBingRedPacket> redPacketSyncList = new ArrayList<>();
        synchronized (lock) {
            for (int i = 0; i < reqMsg.getNum(); i++) {
                redPacketSyncList.add(oneRedPacket(campId, userId, reqMsg.getGoodId()));
            }
        }

        if (redPacketSyncList.size() > 0) {
            syncRedPacketToCampUser(userId, redPacketSyncList);

            // 红点
            for (CrossTongBingUserData tmpUserData : userDataMap.values()) {
                if (tmpUserData.getCampId() == campId) {
                    addRedDot(tmpUserData, eTongBingRedDotType.Type9_GetBox);
                }
            }
        }
    }

    // 买完宝箱礼包，同步所有阵营玩家有礼包了
    public void syncRedPacketToCampUser(long userId, List<CrossTongBingRedPacket> redPacketSyncList) {
        CrossTongBingUserData rpUserData = getUserData(userId);
        UserBaseInfo rpUserBaseInfo = CrossUserMgr.getUserBaseInfo(userId);
        if (rpUserBaseInfo == null) {
            return;
        }
        TongBingProto.CrossTongBingRedPacketSyncMsg.Builder syncMsg = TongBingProto.CrossTongBingRedPacketSyncMsg.newBuilder();
        for (CrossTongBingRedPacket redPacket : redPacketSyncList) {
            TongBingProto.TongBingRedPacketMsg.Builder redPacketMsg = TongBingProto.TongBingRedPacketMsg.newBuilder();
            redPacketMsg.setUserId(userId);
            redPacketMsg.setNickName(rpUserBaseInfo.getNickName());
            redPacketMsg.setUnionName(rpUserBaseInfo.getUnionName());
            redPacketMsg.setType(redPacket.getGoodId());
            redPacketMsg.setIsGet(0);
            redPacketMsg.setOrderNo("" + redPacket.getRedPacketNo());
            syncMsg.addRedPackets(redPacketMsg);
        }
        List<Long> serverIdList = new ArrayList<>();
        for (CrossTongBingUserData userData : userDataMap.values()) {
            if (userData.getCampId() == rpUserData.getCampId()) {
                UserBaseInfo userBaseInfo = CrossUserMgr.getUserBaseInfo(userData.getUserId());
                if (userBaseInfo != null) {
                    syncMsg.addUserIds(userData.getUserId());
                    if (!serverIdList.contains(userBaseInfo.getServerId())) {
                        serverIdList.add(userBaseInfo.getServerId());
                    }
                }
            }
        }
        for (long serverId : serverIdList) {
            MessageHelper.sendPacket(serverId, 0, YanQuMessageUtils.buildMessage(GameProtocol.S_CROSS_TONG_BING_RED_PACKET_SYNC, syncMsg));
        }
    }

    //同步宝箱值去本服改condition
    public void syncRedPacketBoxValue() {
        if (!ActivityMgr.activityInShowTime(config.getActivityInfo())) {
            return;
        }
        for (CrossTongBingCampData campData : new ConcurrentHashMap<>(campDataMap).values()) {
            if (campData.getRedPacketValue() > 0 && campData.getCampId() != 0) {
                Map<Long, List<Long>> serverUserMap = getCampServerIdUserIdMap(campData.getCampId());
                for (long serverId : serverUserMap.keySet()) {
                    TongBingProto.CrossTongBingRedPacketBoxValueSyncMsg.Builder syncMsg = TongBingProto.CrossTongBingRedPacketBoxValueSyncMsg.newBuilder();
                    syncMsg.setActivityId(campData.getActivityId());
                    for (long userId : serverUserMap.get(serverId)) {
                        syncMsg.addUserIds(userId);
                    }
                    syncMsg.setValue(campData.getRedPacketValue());
                    MessageHelper.sendPacket(serverId, 0, YanQuMessageUtils.buildMessage(GameProtocol.S_CROSS_TONG_BING_RED_PACKET_BOX_VALUE_SYNC, syncMsg));
                }
            }
        }
    }

    private Map<Long, List<Long>> getCampServerIdUserIdMap(int campId) {
        Map<Long, List<Long>> resMap = new ConcurrentHashMap<>();
        for (CrossTongBingUserData userData : new ConcurrentHashMap<>(userDataMap).values()) {
            if (userData.getCampId() == campId) {
                UserBaseInfo userBaseInfo = CrossUserMgr.getUserBaseInfo(userData.getUserId());
                if (userBaseInfo != null) {
                    if (!resMap.containsKey(userBaseInfo.getServerId())) {
                        resMap.put(userBaseInfo.getServerId(), new ArrayList<>());
                    }
                    resMap.get(userBaseInfo.getServerId()).add(userData.getUserId());
                }
            }
        }
        return resMap;
    }

    // 新增玩家的红包宝箱信息
    public void addUserRedPacketCount(CrossTongBingRedPacket redPacket, CrossTongBingCampData campData) {
        Map<Long, CrossTongBingUserRedPacketCount> userRedPacketMap = campData.getUserRedPacketMap();
        if (!userRedPacketMap.containsKey(redPacket.getUserId())) {
            CrossTongBingUserRedPacketCount temp = new CrossTongBingUserRedPacketCount();
            temp.setUserId(redPacket.getUserId());
            userRedPacketMap.put(redPacket.getUserId(), temp);
        }
        CrossTongBingUserRedPacketCount userRedPacket = userRedPacketMap.get(redPacket.getUserId());
        Map<Integer, Integer> redPacketCountMap = userRedPacket.getUserRedPacketCountMap();
        redPacketCountMap.put(redPacket.getGoodId(), redPacketCountMap.getOrDefault(redPacket.getGoodId(), 0) + 1);
        userRedPacket.setUserRedPacketCountMap(redPacketCountMap);
    }

    /**
     * 获取红包列表（优先没领取的）
     */
    public void getRedPacketList(long userId) {
        TongBingProto.TongBingRedPacketGetListRespMsg.Builder respMsg = TongBingProto.TongBingRedPacketGetListRespMsg.newBuilder();
        respMsg.setRet(0);

        CrossTongBingUserData userData = getUserData(userId);
        int campId = userData.getCampId();

        // 没资格
        if (!isAble(userData)) {
            respMsg.setRet(GameErrorCode.E_TONG_BING_NO_ABLE);
            sendPacket(ClientProtocol.U_TONG_BING_RED_PACKET_LIST, respMsg, userId);
            return;
        }

        // 统计红包
        LinkedList<CrossTongBingRedPacket> noGetRewardList = new LinkedList<>();
        LinkedList<CrossTongBingRedPacket> getRewardList = new LinkedList<>();
        CrossTongBingCampData campData = campDataMap.get(campId);
        if (campData != null) {
            for (CrossTongBingRedPacket redPacket : campData.getRedPacketMap().values()) {
                if (redPacket.getGetRewardUserList().contains(userId)) {
                    getRewardList.add(redPacket);
                } else {
                    noGetRewardList.add(redPacket);
                }
            }
        }

        // 发送晚的在前
        noGetRewardList.sort(new RedPacketSort());
        getRewardList.sort(new RedPacketSort());

        // 拿前50个
        int maxNum = 50;
        int num = 1;
        for (CrossTongBingRedPacket redPacket : noGetRewardList) {
            if (num >= maxNum) {
                break;
            }
            respMsg.addRedPackets(parseTongBingRedPacketMsg(redPacket, false));
            num++;
        }
        for (CrossTongBingRedPacket redPacket : getRewardList) {
            if (num >= maxNum) {
                break;
            }
            respMsg.addRedPackets(parseTongBingRedPacketMsg(redPacket, true));
            num++;
        }

        sendPacket(ClientProtocol.U_TONG_BING_RED_PACKET_LIST, respMsg, userId);
    }

    class RedPacketSort implements Comparator<CrossTongBingRedPacket> {
        @Override
        public int compare(CrossTongBingRedPacket o1, CrossTongBingRedPacket o2) {
            return Long.compare(o2.getTime(), o1.getTime());
        }
    }

    /**
     * 初始化各阵营红包排行
     */
    public void initUserRedPacketNumMap() {
        for (CrossTongBingCampData campData : campDataMap.values()) {
            Map<Long, CrossTongBingUserRedPacketCount> tempMap = new ConcurrentHashMap<>();
            for (CrossTongBingRedPacket redPacket : campData.getRedPacketMap().values()) {
                if (!tempMap.containsKey(redPacket.getUserId())) {
                    CrossTongBingUserRedPacketCount temp = new CrossTongBingUserRedPacketCount();
                    temp.setUserId(redPacket.getUserId());
                    tempMap.put(redPacket.getUserId(), temp);
                }
                CrossTongBingUserRedPacketCount userRedPacketCount = tempMap.get(redPacket.getUserId());
                Map<Integer, Integer> redPacketCountMap = userRedPacketCount.getUserRedPacketCountMap();
                redPacketCountMap.put(redPacket.getGoodId(), redPacketCountMap.getOrDefault(redPacket.getGoodId(), 0) + 1);
                userRedPacketCount.setUserRedPacketCountMap(redPacketCountMap);
            }
            campData.setUserRedPacketMap(tempMap);
        }
    }


    /**
     * 红包发送记录
     */
    public void getRedPacketRecord(long userId) {
        TongBingProto.TongBingRedPacketGetRecordRespMsg.Builder respMsg = TongBingProto.TongBingRedPacketGetRecordRespMsg.newBuilder();
        respMsg.setRet(0);

        CrossTongBingUserData userData = getUserData(userId);
        if (!isAble(userData)) {
            respMsg.setRet(GameErrorCode.E_TONG_BING_NO_ABLE);
            sendPacket(ClientProtocol.U_TONG_BING_RED_PACKET_RECORD_LIST, respMsg, userId);
            return;
        }

        CrossTongBingCampData campData = campDataMap.get(userData.getCampId());
        if (campData != null) {
            Map<Long, CrossTongBingUserRedPacketCount> userRedPacketMap = campData.getUserRedPacketMap();
            for (CrossTongBingUserRedPacketCount userRedPacket : userRedPacketMap.values()) {
                UserBaseInfo userBaseInfo = CrossUserMgr.getUserBaseInfo(userRedPacket.getUserId());
                if (userBaseInfo == null) {
                    userBaseInfo = new UserBaseInfo();
                }

                TongBingProto.TongBingRedPacketRankMsg.Builder rankTempMsg = TongBingProto.TongBingRedPacketRankMsg.newBuilder();
                rankTempMsg.setUserId(userRedPacket.getUserId());
                rankTempMsg.setNickName(userBaseInfo.getNickName());
                for (Map.Entry<Integer, Integer> entry : userRedPacket.getUserRedPacketCountMap().entrySet()) {
                    TongBingProto.TongBingRedPacketBoxMsg.Builder boxTempMsg = TongBingProto.TongBingRedPacketBoxMsg.newBuilder();
                    boxTempMsg.setBoxKey(entry.getKey());
                    boxTempMsg.setNum(entry.getValue());
                    rankTempMsg.addBox(boxTempMsg);
                }
                respMsg.addRecords(rankTempMsg);
            }
        }

        sendPacket(ClientProtocol.U_TONG_BING_RED_PACKET_RECORD_LIST, respMsg, userId);
    }


    /**
     * 领取阵营宝箱
     */
    public void getRedPacketBox(long userId, TongBingProto.CrossTongBingRedPacketGetBoxReqMsg reqMsg) {
        TongBingProto.CrossTongBingRedPacketGetBoxRespMsg.Builder serverMsg = TongBingProto.CrossTongBingRedPacketGetBoxRespMsg.newBuilder();
        serverMsg.setRet(0);

        // 资格
        CrossTongBingUserData userData = getUserData(userId);
        if (!isAble(userData)) {
            serverMsg.setRet(GameErrorCode.E_TONG_BING_NO_ABLE);
            sendPacket(GameProtocol.S_CROSS_TONG_BING_RED_PACKET_GET_BOX, serverMsg, userId);
            return;
        }

        // 数据判断
        CrossTongBingCampData campData = campDataMap.get(userData.getCampId());
        if (campData == null) {
            serverMsg.setRet(GameErrorCode.E_TONG_BING_RED_PACKET_IS_GET);
            sendPacket(GameProtocol.S_CROSS_TONG_BING_RED_PACKET_GET_BOX, serverMsg, userId);
            return;
        }

        long boxNo = StringUtils.isNullOrEmpty(reqMsg.getOrderNo()) ? 0 : Long.valueOf(reqMsg.getOrderNo());
        Map<Integer, TongBingCampBoxConfig> campBoxConfigMap = config.getCampBoxConfigMap();
        Map<Long, CrossTongBingRedPacket> redPacketMap = campData.getRedPacketMap();

        synchronized (lock) {

            if (boxNo == 0) {
                // 【一键领取】
                Property reward = new Property();
                for (CrossTongBingRedPacket redPacket : redPacketMap.values()) {
                    Set<Long> getRewardUserList = redPacket.getGetRewardUserList();
                    if (!getRewardUserList.contains(userId)) {
                        TongBingCampBoxConfig campBoxConfig = campBoxConfigMap.get(redPacket.getGoodId());
                        reward.addProperty(campBoxConfig.getBoxId(), BigInteger.ONE);
                        getRewardUserList.add(userId);
                        campData.setRedPacketValue(campData.getRedPacketValue() + campBoxConfig.getBoxScore());
                    }
                }
                String rewardStr = PropertyHelper.parsePropertyToString(reward);
                if (StringUtils.isNullOrEmpty(rewardStr)) {
                    serverMsg.setRet(GameErrorCode.E_TONG_BING_RED_PACKET_IS_GET);
                    sendPacket(GameProtocol.S_CROSS_TONG_BING_RED_PACKET_GET_BOX, serverMsg, userId);
                    return;
                }
                serverMsg.setReward(rewardStr);
                sendPacket(GameProtocol.S_CROSS_TONG_BING_RED_PACKET_GET_BOX, serverMsg, userId);
                campData.setUpdateOption();
            } else {
                // 【单个领取】
                CrossTongBingRedPacket redPacket = redPacketMap.get(boxNo);
                if (redPacket == null) {
                    serverMsg.setRet(GameErrorCode.E_TONG_BING_RED_PACKET_NO_EXIT);
                    sendPacket(GameProtocol.S_CROSS_TONG_BING_RED_PACKET_GET_BOX, serverMsg, userId);
                    return;
                }
                Set<Long> getRewardList = redPacket.getGetRewardUserList();
                if (getRewardList.contains(userId)) {
                    serverMsg.setRet(GameErrorCode.E_TONG_BING_RED_PACKET_IS_GET);
                    sendPacket(GameProtocol.S_CROSS_TONG_BING_RED_PACKET_GET_BOX, serverMsg, userId);
                    return;
                }
                TongBingCampBoxConfig campBoxConfig = campBoxConfigMap.get(redPacket.getGoodId());
                getRewardList.add(userId);
                campData.setRedPacketValue(campData.getRedPacketValue() + campBoxConfig.getBoxScore());
                serverMsg.setReward(campBoxConfig.getBoxId() + "=1");
                // 一键领取的时候还要下去连大宝箱都领了
                serverMsg.setBoxValue(campData.getRedPacketValue());
                serverMsg.setActivityId(config.getActivityInfo().getActivityId());
                sendPacket(GameProtocol.S_CROSS_TONG_BING_RED_PACKET_GET_BOX, serverMsg, userId);
                campData.setUpdateOption();
            }
        }
    }

    /**
     * 计算全部兵营
     */
    public void calcAllBarracks() {
        for (CrossTongBingUserData userData : userDataMap.values()) {

            CrossTongBingBarracks barracks = userData.getBarracks();
            if (isAble(userData) && barracks.getReadyMaxNum() > 0) {

                barracksCalc(userData.getUserId());

                // 红点
                if (barracks.getCreateNum() == 0 && barracks.getReadyNum() < barracks.getReadyMaxNum()) {
                    addRedDot(userData, eTongBingRedDotType.Type4_BarracksFree);
                }
                if (barracks.getWoundedNum() == 0 && barracks.getRescueNum() > 0
                        || barracks.getRescueNum() >= config.getRedDotCureNum()) {
                    addRedDot(userData, eTongBingRedDotType.Type6_Wounded);
                }
            }
        }
    }

    /**
     * 重新计算最大兵营数，来触发时候要有buff，开始和失效时候触发 Type6_ReadySoldierMaxNum
     */
    private void recalculateBarracksReadyMaxNum(long userId) {
        CrossTongBingUserData userData = getUserData(userId);
        CrossTongBingBarracks barracks = userData.getBarracks();
        barracks.setReadyMaxNum(getBarracksReadyMaxNum(userId));
        userData.setUpdateOption();
    }

    /**
     * 重新计算城池产量，开始的时候buff不能有，结束的时候buff不能失效 Type5_CityFoodProduce
     *
     * @param campId
     * @param dealTime buff开始或者结束的时间点
     */
    private void recalculateCityFoodProdProduction(int campId, long dealTime) {
        makeAllCityCollect(campId, dealTime);
    }

    /**
     * 重新计算医院最大伤兵数，来触发时候要有buff，开始和失效时候触发 Type4_Hospital
     *
     * @param userId
     */
    private void recalculateBarracksHospitalWoundedMaxNum(long userId) {
        CrossTongBingUserData userData = getUserData(userId);
        CrossTongBingBarracks barracks = userData.getBarracks();
        barracks.setHospitalSize(getBarracksWoundedMaxNum(userId));
        userData.setUpdateOption();
    }

    /**
     * 重新计算每次治疗伤兵数
     */
    private void recalculateBarracksHospitalWoundedCount(long userId) {
        CrossTongBingUserData userData = getUserData(userId);
        CrossTongBingBarracks barracks = userData.getBarracks();
        barracks.setRescueCount(getBarracksWoundedCount(userId));
        userData.setUpdateOption();
    }

    /**
     * 重新计算医院恢复伤兵，开始的时候buff不能有，结束的时候buff不能失效 Type4_Hospital
     */
    private void recalculateBarracksHospitalRecovery(long userId) {
        barracksCalc(userId);
    }

    /**
     * 计算军营预备兵上限
     */
    private long getBarracksReadyMaxNum(long userId) {
        // 拿队伍带兵上限总和
        long allValue = 0L;
        CrossTongBingUserData userData = getUserData(userId);
        for (CrossTongBingTeam team : userData.getTeamMap().values()) {
            allValue = allValue + team.getMaxSoldier();
        }
        // 乘以参数
        allValue = allValue * config.getBarrackLimitParam() / 1000;
        // 加buff
        Integer buffAddition = getBuffAddition(userData.getUserId(), eTongBingBuffType.Type6_ReadySoldierMaxNum).get(0);
        allValue = BigDecimal.valueOf(allValue)
                .multiply(BigDecimal.valueOf(1000 + buffAddition)).divide(BigDecimal.valueOf(1000), 0, BigDecimal.ROUND_DOWN).longValue();
        return allValue;
    }

    //获取最大的军营医院人数
    private long getBarracksWoundedMaxNum(long userId) {
        long allValue = config.getWoundedSoldierMaxNum();
        CrossTongBingUserData userData = getUserData(userId);
        Integer buffAddition = getBuffAddition(userData.getUserId(), eTongBingBuffType.Type4_Hospital).get(0);
        allValue = BigDecimal.valueOf(allValue)
                .multiply(BigDecimal.valueOf(1000 + buffAddition)).divide(BigDecimal.valueOf(1000), 0, BigDecimal.ROUND_DOWN).longValue();
        return allValue;
    }

    // 获得伤兵恢复个数
    private long getBarracksWoundedCount(long userId) {
        //伤兵加上buff修复速度
        Integer buffAddition = getBuffAddition(userId, eTongBingBuffType.Type4_Hospital).get(1);
        return BigDecimal.valueOf(config.getWoundedRescueCount())
                .multiply(BigDecimal.valueOf(1000 + buffAddition))
                .divide(BigDecimal.valueOf(1000), 0, BigDecimal.ROUND_DOWN).longValue();
    }

    @Override
    public CrossTongBingUserData getUserData(long userId) {
        initUserData(userId);
        CrossTongBingUserData userData = userDataMap.get(userId);
        return userData;
    }

    public CrossTongBingUnionJoin getUnionJoin(String unionUid) {
        return unionJoinMap.get(unionUid);
    }

    public CrossTongBingData getCrossTongBingData() {
        return crossTongBingData;
    }

    public int getGroupId() {
        return groupId;
    }

    // ============================== 定时器 ==============================

    /**
     * 初始化所有Task
     */
    public void initTask() {

        // 空事件
        addTask(new CrossTongBingEmptyTask(this, System.currentTimeMillis() + DateHelper.SECOND_MILLIONS * 10));

        // 商会报名锁定
        long checkLockTime = Math.min(System.currentTimeMillis(), config.getJoinTimeList().get(1)); // 锁名单前，要检查下其他数据
        addTask(new CrossTongBingLockUnionTask(this, checkLockTime));

        // 阶段切换移动玩家
        long stage1Begin = config.getStage1TimeList().get(0).get(0);
        addTask(new CrossTongBingInitTeamPosTask(this, stage1Begin));

        // 城池事件刷新
        long refreshTime = getNextCityEventTime(getCrossTongBingData().getRefreshCityEventTime(), true);
        addTask(new CrossTongBingRefreshCityEventTask(this, refreshTime));

        // 城池事件生效
        long applyTime = getNextCityEventTime(getCrossTongBingData().getApplyCityEventTime(), false);
        addTask(new CrossTongBingApplyCityEventTask(this, applyTime));

        // Buff结束
        for (List<CrossTongBingCampBuff> buffList : getCrossTongBingData().getCampBuffMap().values()) {
            for (CrossTongBingCampBuff buff : buffList) {
                addTask(new CrossTongBingBuffExpireTask(this, getBuffEndTime(buff)));
            }
        }

        // 战斗事件
        for (CrossTongBingCity city : getCrossTongBingData().getCityMap().values()) {
            if (city.getBattlefield() != null) {
                addTask(new CrossTongBingBattleTask(this, calcNextBattleTime(city), city.getCityId()));
            }
        }

        // 移动事件
        for (CrossTongBingUserData userData : userDataMap.values()) {
            if (isAble(userData)) {
                for (CrossTongBingTeam team : userData.getTeamMap().values()) {
                    if (!team.getRouteList().isEmpty()) {
                        addTask(new CrossTongBingMoveTask(this, team.getArriveTime(), userData.getUserId(), team.getOccupation()));
                    }
                }
            }
        }

        // 城防军恢复
        for (CrossTongBingCity city : crossTongBingData.getCityMap().values()) {
            if (city.getTeamRecoveryBeginTime() != 0) {
                addTask(new CrossTongBingCityTeamRecoveryTask(this, city.getTeamRecoveryBeginTime() + config.getCityTeamRecoveryTime(), city.getCityId()));
            }
            if (city.getCampId() != 0) {
                addTask(new CrossTongBingRecoveryExtraTeamTask(this, getNextRecoveryExtraTeamTime(city), city.getCityId()));
            }
        }

        // 发商会荣誉（可重复调用）
        addTask(new CrossTongBingSendUnionHonorTask(this, config.getActivityInfo().getEndTime() * 1000L + DateHelper.MINUTE_MILLIONS * 5));

        // 同步移动数据
        addTask(new CrossTongBingSyncTeamTask(this));

        // 同步城池数据
        addTask(new CrossTongBingSyncCityTask(this));

        // 设置城主
        addTask(new CrossTongBingSetWinnerTask(this, config.getActivityInfo().getEndTime() * 1000L));
    }

    /**
     * 空事件，10秒一次
     */
    public void crossTongBingEmptyTask() {
        addTask(new CrossTongBingEmptyTask(this, System.currentTimeMillis() + DateHelper.SECOND_MILLIONS * 10));
    }

    /**
     * 商会报名、锁名单 任务
     */
    public void crossTongBingLockUnionTask() {
        // 【已经锁名单了】
        if (getCrossTongBingData().isLockUnion()) {
            return;
        }

        long now = System.currentTimeMillis();

        // 【没锁名单】先把任务加回去比较稳
        long next10Second = System.currentTimeMillis() + DateHelper.SECOND_MILLIONS * 60;
        addTask(new CrossTongBingLockUnionTask(this, next10Second));

        boolean canLock = true;

        // 【没到锁名单时间】
        if (now < config.getJoinTimeList().get(1)) {
            // 检查已有商会的成员名单
            List<String> noMemberList = new ArrayList<>();
            for (CrossTongBingUnionJoin unionJoin : unionJoinMap.values()) {
                if (unionJoin.getMemberMap().isEmpty()) {
                    noMemberList.add(unionJoin.getUnionUid());
                }
            }
            if (noMemberList.size() > 0) {
                sendGetUnionMsg(1, noMemberList, 0);
            }
            // 检查成员有没有赚钱
            checkUnionJoinMember(null);

            canLock = false;
        }

        // 【到截止时间 && 未到下一阶段开始前10分钟】
        if (now > config.getJoinTimeList().get(1) && now < config.getStage1TimeList().get(0).get(0) - DateHelper.MINUTE_MILLIONS * 10) {

            // 商会数量不足：拉赚速前x名商会
            if (unionJoinMap.size() < config.getUnionNum()) {
                sendGetUnionMsg(2, new ArrayList<>(), config.getUnionNum());
                logger.info("tongbing activity {} cur union num {}, need union num {}. try repeated request union data.",
                        config.getActivityInfo().getActivityId(), unionJoinMap.size(), config.getUnionNum());

                canLock = false;
            }

            // 商会成员数据不足：继续请求
            if (!checkUnionJoinMember(null)) {
                logger.info("tongbing activity {} check union member data no success, some user have not data. try repeated request user data.",
                        config.getActivityInfo().getActivityId());

                canLock = false;
            }

        }

        if (canLock) {
            // 【到这里可以安心的锁定了】
            lockUnion();
        }
    }

    /**
     * 初始化队伍位置
     */
    public void crossTongBingInitTeamPosTask() {
        long now = System.currentTimeMillis();

        // 【没锁榜】过会儿再看看
        if (!getCrossTongBingData().isLockUnion()) {
            addTask(new CrossTongBingInitTeamPosTask(this, now + DateHelper.SECOND_MILLIONS * 10));
            return;
        }

        // 【强制移动没city的队伍】
        for (CrossTongBingUserData userData : userDataMap.values()) {
            if (userData.getCampId() == 0) {
                continue; // 没阵营跳过
            }
            for (CrossTongBingTeam team : userData.getTeamMap().values()) {
                if (team.getCityId() == 0) {
                    team.setRouteList(Collections.singletonList(getBirthCityId(userData.getCampId())));
                    forceArriveNextCity(team, System.currentTimeMillis());
                    userData.setUpdateOption();
                }
            }
        }

        // 【定时检查一下】
        addTask(new CrossTongBingInitTeamPosTask(this, now + DateHelper.MINUTE_MILLIONS * 5));
    }

    /**
     * 没数据的成员拉一下
     *
     * @param optionalUnionUid 可以为null，就检查全部商会
     */
    private boolean checkUnionJoinMember(String optionalUnionUid) {
        // 检查报名的商会中，区服未上传数据的成员
        Map<Long, List<Long>> serverUserMap = new ConcurrentHashMap<>();
        for (CrossTongBingUnionJoin unionJoin : unionJoinMap.values()) {
            if (optionalUnionUid != null && !Objects.equals(unionJoin.getUnionUid(), optionalUnionUid)) {
                continue;
            }
            for (CrossTongBingUnionJoinMember joinMember : unionJoin.getMemberMap().values()) {
                if (joinMember.getEarnSpeed() == 0 || joinMember.getTopAbility() == 0) {
                    serverUserMap.putIfAbsent(joinMember.getServerId(), new ArrayList<>());
                    serverUserMap.get(joinMember.getServerId()).add(joinMember.getUserId());
                }
            }
        }
        boolean suc = true;
        for (Map.Entry<Long, List<Long>> entry : serverUserMap.entrySet()) {
            long serverId = entry.getKey();
            List<Long> userId = entry.getValue();
            if (userId.size() > 0) {
                sendGetUnionMemberMsg(serverId, userId);
                suc = false;
            }
        }
        return suc;
    }

    /**
     * 战斗任务
     */
    public void crossTongBingBattleTask(int cityId) {
        synchronized (lock) {

            // 是否没战斗了
            CrossTongBingCity city = getCrossTongBingData().getCityMap().get(cityId);
            CrossTongBingBattlefield battlefield = city.getBattlefield();
            if (battlefield == null) {
                return;
            }

            // 时间到了没有
            long battleTime = calcNextBattleTime(city);
            if (System.currentTimeMillis() < battleTime) {
                addTask(new CrossTongBingBattleTask(this, battleTime, cityId));
                return;
            }

            // 处理战斗
            List<CrossTongBingBattleResult> resultList = new ArrayList<>();
            for (CrossTongBingBattlefieldPart part : battlefield.getPartMap().values()) {
                CrossTongBingBattleResult battleResult = handleBattle(city, battlefield, part, battleTime);
                if (battleResult != null) {
                    resultList.add(battleResult);
                }
            }

            // 设置上次战斗时间
            int drumTimes = battlefield.getDrumTimes();
            if (drumTimes > 0) {
                battlefield.setDrumTimes(drumTimes - 1);
            }
            city.setBattledNum(city.getBattledNum() + resultList.size());
            battlefield.setBattledNum(battlefield.getBattledNum() + resultList.size());
            battlefield.setLastBattleTime(battleTime);

            // 士兵进入战场
            teamToBattlefield(city, false);

            // 移除空战场
            List<Integer> removePartId = removeEmptyBattlefield(city);

            // 判断城池战报
            CrossTongBingCityWarReport cityWarReport = null;
            if (battlefield.getPartMap().isEmpty()) {
                cityWarReport = recordBattleReport(city, battleTime); // 记录战报
            }

            // 同步战斗结果
            syncBattleResultMsg(resultList, cityWarReport);

            // 战斗结束处理
            if (cityWarReport != null) {
                handleBattleFinish(city, battleTime); // 判断城池换主人
                city.setBattlefield(null); // 移除战场
                chatLogic.clearBattlefieldChat(cityId); // 清空战场聊天
            }

            // 判断新的大战场
            if (city.getAtkTeamList().size() > 0 && city.getBattlefield() == null) {
                createBattlefield(city);
            }

            // 是否继续战斗
            if (city.getBattlefield() != null) {
                addTask(new CrossTongBingBattleTask(this, calcNextBattleTime(city), city.getCityId()));
            } else {
                syncCityTeamToSelf(city);
            }

            crossTongBingData.setUpdateOption();

            // 同步城池
            addNeedSyncCity(cityId);

            // 清空场景
            for (Integer partId : removePartId) {
                CrossSceneMgr.removeScene(config.getActivityInfo().getActivityId(), eSceneType.TongBingBattlefield, getBattlefieldSceneKey(cityId, partId));
            }
        }
    }

    /**
     * 移动任务
     */
    public void crossTongBingMoveTask(long userId, int teamId) {
        CrossTongBingUserData userData = getUserData(userId);
        CrossTongBingTeam team = userData.getTeamMap().get(teamId);
        synchronized (lock) {

            // 判断状态
            if (getTeamStatus(team) != eTongBingTeamStatus.Move) {
                return;
            }

            long now = System.currentTimeMillis();

            // 时间没到
            if (now < team.getArriveTime()) {
                addTask(new CrossTongBingMoveTask(this, team.getArriveTime(), userId, teamId));
                return;
            }

            // 队伍到达城池
            forceArriveNextCity(team, team.getArriveTime());
        }
    }

    /**
     * 刷新城池任务
     */
    public void crossTongBingRefreshCityEventTask() {

        boolean create = false;
        long refreshTime;

        synchronized (lock) {
            CrossTongBingData crossTongBingData = getCrossTongBingData();

            long now = System.currentTimeMillis();
            refreshTime = getNextCityEventTime(crossTongBingData.getRefreshCityEventTime(), true);

            // 时间还没到
            if (now < refreshTime) {
                addTask(new CrossTongBingRefreshCityEventTask(this, refreshTime));
                return;
            }

            // 刷新前，移除旧的
            Map<Integer, CrossTongBingCityEvent> cityEventMap = crossTongBingData.getCityEventMap();
            for (Map.Entry<Integer, CrossTongBingCityEvent> entry : cityEventMap.entrySet()) {
                CrossTongBingCityEvent event = entry.getValue();
                if (event.getEffectCamp() != 0 && refreshTime > event.getEndTime()) {
                    cityEventMap.remove(entry.getKey());
                }
            }

            // 看哪些buff没被用掉
            List<Integer> allCampKey = new ArrayList<>();
            for (int i = 1; i <= config.getCampNum(); i++) {
                allCampKey.add(i);
            }
            for (CrossTongBingCityEvent cityEvent : cityEventMap.values()) {
                // 未生效
                if (cityEvent.getEffectCamp() == 0) {
                    cityEvent.setRefreshTime(refreshTime); // 重设刷新时间
                    allCampKey.remove(Integer.valueOf(cityEvent.getCampKey()));
                }
            }

            // 【生成】
            for (Integer campKey : allCampKey) {
                List<Integer> cityIdList = getMiddleCityId(campKey, campKey == config.getCampNum() ? 1 : campKey + 1);

                // 尽量不相同
                List<Integer> newCityIdList = new ArrayList<>(cityIdList);
                newCityIdList.removeAll(cityEventMap.values().stream().map(CrossTongBingCityEvent::getCityId).collect(Collectors.toList()));

                List<Integer> list = newCityIdList.size() > 0 ? newCityIdList : cityIdList;
                // 移除掉中心城和主城
                for (TongBingCityConfig config : config.getCityConfigMap().values()) {
                    if (config.getType() == 1 || config.getType() == 6) {
                        list.remove(Integer.valueOf(config.getId()));
                    }
                }
                // 不让他报错
                if (list.isEmpty()) {
                    for (int i = 0; i < 10; i++)
                        logger.error("activityId = {}, camp key = {}, mid city is empty!!!",
                                config.getActivityInfo().getActivityId(), campKey);
                    continue;
                }

                // 生成事件
                Integer cityId = list.get(randomHelper.next(list.size()));
                List<Integer> eventConfigList = config.getCityEventConfigList();
                int buffId = eventConfigList.get(randomHelper.next(eventConfigList.size()));
                cityEventMap.put(campKey, new CrossTongBingCityEvent(campKey, cityId, buffId, refreshTime));

                create = true;
            }

            // 设置下次刷新
            crossTongBingData.setRefreshCityEventTime(refreshTime);
            addTask(new CrossTongBingRefreshCityEventTask(this, getNextCityEventTime(refreshTime, true)));

            crossTongBingData.setUpdateOption();
        }

        // 同步在线玩家
        syncCityEventToAll();
        // 红点全亮
        for (CrossTongBingUserData userData : userDataMap.values()) {
            if (isAble(userData)) {
                addRedDot(userData, eTongBingRedDotType.Type7_CityEventRefresh);
            }
        }

        // 日志
        for (CrossTongBingCityEvent cityEvent : getCrossTongBingData().getCityEventMap().values()) {
            CrossAutoLogMgr.add(new LogTongBingCityEvent(
                    config.getActivityInfo().getActivityId(),
                    1,
                    refreshTime,
                    cityEvent.getCityId(),
                    JSON.toJSONString(cityEvent),
                    groupId
            ));
        }

    }

    /**
     * 生效城池任务
     */
    public void crossTongBingApplyCityEventTask() {

        long applyTime;

        synchronized (lock) {

            CrossTongBingData crossTongBingData = getCrossTongBingData();

            long now = System.currentTimeMillis();
            applyTime = getNextCityEventTime(crossTongBingData.getApplyCityEventTime(), false);

            // 时间还没到
            if (now < applyTime) {
                addTask(new CrossTongBingApplyCityEventTask(this, applyTime));
                return;
            }

            boolean change = false;
            Map<Integer, CrossTongBingCityEvent> cityEventMap = crossTongBingData.getCityEventMap();

            // 生效buff
            for (CrossTongBingCityEvent cityEvent : cityEventMap.values()) {

                // 跳过已生效的buff
                if (cityEvent.getEffectCamp() != 0) {
                    continue;
                }

                CrossTongBingCity city = crossTongBingData.getCityMap().get(cityEvent.getCityId());
                if (city.getCampId() != 0) {

                    CrossTongBingCampBuff buff = new CrossTongBingCampBuff(
                            city.getCampId(),
                            applyTime,
                            city.getCityId(),
                            cityEvent.getBuffId()
                    );

                    // buff生效前操作
                    beforeBeginBuff(buff);

                    // 添加buff结束任务
                    long buffEndTime = getBuffEndTime(buff);
                    addTask(new CrossTongBingBuffExpireTask(this, buffEndTime));

                    // 加buff
                    Map<Integer, List<CrossTongBingCampBuff>> campBuffMap = crossTongBingData.getCampBuffMap();
                    ArrayList<CrossTongBingCampBuff> list = new ArrayList<>(campBuffMap.getOrDefault(city.getCampId(), new ArrayList<>()));
                    list.add(buff);
                    campBuffMap.put(city.getCampId(), list);

                    // 记录生效
                    cityEvent.setEffectCamp(city.getCampId());
                    cityEvent.setEndTime(getBuffEndTime(buff));

                    // buff生效后操作
                    afterBeginBuff(buff);

                    change = true;
                }
            }

            // 改时间
            crossTongBingData.setApplyCityEventTime(applyTime);
            // 下个任务
            addTask(new CrossTongBingApplyCityEventTask(this, getNextCityEventTime(crossTongBingData.getApplyCityEventTime(), false)));

            syncCityEventToAll();
            syncBuffToAll();

            crossTongBingData.setUpdateOption();
        }

        // 日志
        for (CrossTongBingCityEvent cityEvent : getCrossTongBingData().getCityEventMap().values()) {
            CrossAutoLogMgr.add(new LogTongBingCityEvent(
                    config.getActivityInfo().getActivityId(),
                    2,
                    applyTime,
                    cityEvent.getCityId(),
                    JSON.toJSONString(cityEvent),
                    groupId
            ));
        }
    }

    /**
     * 过期失效buff
     */
    public void crossTongBingBuffExpireTask() {
        long now = System.currentTimeMillis();
        long minEndTime = Long.MAX_VALUE;
        Map<Integer, List<CrossTongBingCampBuff>> campBuffMap = getCrossTongBingData().getCampBuffMap();
        for (Map.Entry<Integer, List<CrossTongBingCampBuff>> entry : campBuffMap.entrySet()) {
            List<CrossTongBingCampBuff> buffList = entry.getValue();
            for (int i = 0; i < buffList.size(); i++) {
                CrossTongBingCampBuff campBuff = buffList.get(i);
                long endTime = getBuffEndTime(campBuff);
                if (now >= endTime) {
                    // 结束buff
                    beforeEndBuff(campBuff);
                    buffList.remove(i--);
                    afterEndBuff(campBuff);
                } else {
                    minEndTime = Math.min(endTime, minEndTime);
                }
            }
        }
        if (minEndTime != Long.MAX_VALUE) {
            addTask(new CrossTongBingBuffExpireTask(this, minEndTime));
        }

        getCrossTongBingData().setUpdateOption();
    }

    /**
     * 城防军恢复
     */
    public void crossTongBingCityTeamRecoveryTask(int cityId) {
        synchronized (lock) {

            long now = System.currentTimeMillis();

            CrossTongBingCity city = getCrossTongBingData().getCityMap().get(cityId);
            if (city.getTeamRecoveryBeginTime() == 0) {
                // 恢复过了，或者被刷新了
                return;
            }

            long recoveryTime = city.getTeamRecoveryBeginTime() + config.getCityTeamRecoveryTime();
            if (now < recoveryTime) {
                // 时间还没到
                addTask(new CrossTongBingCityTeamRecoveryTask(this, recoveryTime, cityId));
                return;
            }

            if (city.getBattlefield() != null) {
                // 正在战斗
                city.setTeamRecoveryBeginTime(0);
                return;
            }

            TongBingCityConfig cityConfig = config.getCityConfigMap().get(cityId);
            TongBingCityTypeConfig cityTypeConfig = config.getCityTypeConfigMap().get(cityConfig.getType());

            int num = cityTypeConfig.getTeamNum() + getBuffAddition(city.getCampId(), eTongBingBuffType.Type3_CityTeamMaxNum).get(0);

            // 恢复
            if (city.getTeamNum() < num) {
                city.setTeamNum(num);
            }
            city.setTeamRecoveryBeginTime(0);
            // 战斗场次清空
            city.setBattledNum(0);
            getCrossTongBingData().setUpdateOption();
        }
    }

    /**
     * 获取下次恢复额外守军的时间
     */
    private long getNextRecoveryExtraTeamTime(CrossTongBingCity city) {
        // 间隔一小时
        long lastRecoveryExtraTeamTime = city.getLastRecoveryExtraTeamTime() == 0 ? config.getStage1TimeList().get(0).get(0) : city.getLastRecoveryExtraTeamTime();
        return lastRecoveryExtraTeamTime + DateHelper.HOUR_MILLIONS;
    }

    /**
     * 恢复额外守军
     */
    public void crossTongBingRecoveryExtraTeamTask(int cityId) {
        synchronized (lock) {
            long now = System.currentTimeMillis();
            CrossTongBingCity city = getCrossTongBingData().getCityMap().get(cityId);
            long recoveryTime = getNextRecoveryExtraTeamTime(city);
            if (now < recoveryTime) {
                // 时间还没到
                addTask(new CrossTongBingRecoveryExtraTeamTask(this, recoveryTime, cityId));
                return;
            }
            // 恢复额外守军
            int cityType = config.getCityConfigMap().get(cityId).getType();
            int extraTeamNum = config.getCityTypeConfigMap().get(cityType).getExtraTeamNum();
            city.setLastRecoveryExtraTeamTime(recoveryTime);
            city.setExtraTeamNum(city.getExtraTeamNum() + extraTeamNum);
            getCrossTongBingData().setUpdateOption();
            // 恢复的守军进入战场
            if (city.getBattlefield() != null) {
                teamToBattlefield(city, true);
            }
            // 下一次恢复时间
            addTask(new CrossTongBingRecoveryExtraTeamTask(this, getNextRecoveryExtraTeamTime(city), city.getCityId()));
            logger.info("额外城防军恢复,cityId: {},当前额外城防军数量: {}", cityId, city.getExtraTeamNum());
        }
    }


    /**
     * 加商会旗帜
     */
    public void crossTongBingSendUnionHonorTask() {
        synchronized (lock) {

            long now = System.currentTimeMillis();

            // 超过时间了
            if (now > config.getActivityInfo().getEndShowTime() * 1000L) {
                return;
            }

            long getRewardTime = config.getActivityInfo().getEndTime() * 1000L + DateHelper.MINUTE_MILLIONS * 5;

            // 没到时间
            if (now < getRewardTime) {
                addTask(new CrossTongBingSendUnionHonorTask(this, getRewardTime));
                return;
            }

            // 没人
            LinkedList<CrossTongBingRank> rankList = getCampScoreRankList();
            if (rankList.isEmpty()) {
                logger.error("getCampScoreRankList is empty, can not add union honor !!!!!!!!!!");
                return;
            }

            // 没商会
            int campId = Integer.valueOf(rankList.getFirst().getId());
            CrossTongBingRankListModel rankListModel = campUnionRankMap.get(campId);
            if (rankListModel == null || rankListModel.getRankList().isEmpty()) {
                logger.error("campUnionRankMap campId {} is null or empty, can not add union honor !!!!!!!!!!", campId);
                return;
            }

            // 可以发了
            /*ActivityInfo activityInfo = config.getActivityInfo();
            List<Long> serverIdList = activityInfo.getServerIdList();
            //找出最多要前几名领奖
            int lowestRewardRank = CrossUnionHonorsActivityMgr.getLowestRewardRank(serverIdList.size(), eHonorsActivityType.UNION_HONORS_TONG_BING);
            // 可以发了 找出前n名商会
            List<String> topUnions = getTopUnions(lowestRewardRank);

            CrossUnionHonorsActivityMgr.add(
                    activityInfo.getActivityId(),
                    topUnions,
                    eActivityType.TongBingActivity.getValue(),
                    1,
                    serverIdList,
                    serverIdList.get(0)
            );*/

        }
    }

    /**
     * 同步队伍
     */
    public void crossTongBingSyncTeamTask() {
        addTask(new CrossTongBingSyncTeamTask(this));
        syncNeedSyncTeam();
    }

    /**
     * 定时同步城池
     */
    public void crossTongBingSyncCityTask() {
        addTask(new CrossTongBingSyncCityTask(this));
        syncNeedSyncCity();
    }

    /**
     * 设置城主
     */
    public void crossTongBingSetWinnerTask() {
        // 已经设置城主
        if (crossTongBingData.getProcess2WinUserId() != 0L) {
            return;
        }
        // 时间未到
        long nowTime = System.currentTimeMillis();
        if (nowTime < config.getActivityInfo().getEndTime() * 1000L) {
            addTask(new CrossTongBingSetWinnerTask(this, config.getActivityInfo().getEndTime() * 1000L));
            return;
        }
        // 时间过了
        if (nowTime > config.getActivityInfo().getEndShowTime() * 1000L) {
            return;
        }
        // 正常设置
        Map<Integer, TongBingCityConfig> cityConfigMap = config.getCityConfigMap();
        for (TongBingCityConfig cityConfig : cityConfigMap.values()) {
            if (cityConfig.getType() == 6) {
                Map<Integer, CrossTongBingCity> cityMap = crossTongBingData.getCityMap();
                CrossTongBingCity cityData = cityMap.get(cityConfig.getId());
                int campId = cityData.getCampId();
                long winnerUserId = 0L;
                if (campId == 0) {
                    // 没有城主
                    logger.error("tong bing activityId {} no winner!", config.getActivityInfo().getActivityId());
                    return;
                } else {
                    winnerUserId = getCampMaxZhanGongUserId(campId);
                }
                if (winnerUserId == 0L) {
                    return;
                }
                crossTongBingData.setProcess2WinUserId(winnerUserId);
                CrossTongBingUserData userData = getUserData(winnerUserId);
                userData.setPositionId(eTongBingPosition.Master.getValue());
                UserBaseInfo userBaseInfo = CrossUserMgr.getUserBaseInfo(winnerUserId);
                if (userBaseInfo != null) {
                    TongBingProto.CrossTongBingCityWinerAppointRespMsg.Builder crespMsg = TongBingProto.CrossTongBingCityWinerAppointRespMsg.newBuilder();
                    crespMsg.setWinerName("");
                    crespMsg.setPosition(userData.getPositionId());
                    crespMsg.setSendReward(true);
                    MessageHelper.sendPacket(userBaseInfo.getServerId(), winnerUserId, YanQuMessageUtils.buildMessage(GameProtocol.S_CROSS_TONG_BING_CITY_APPOINT_POSITION, crespMsg));
                }
                syncWinnerGetNotice();
                return;
            }
        }
    }


    // ============================== 榜单 ==============================

    /**
     * 初始化全部榜单
     */
    private void initAllRankList() {
        CrossTongBingRankListModel userModel = new CrossTongBingRankListModel();
        Map<Integer, CrossTongBingRankListModel> campUserMap = new ConcurrentHashMap<>();
        Map<Integer, CrossTongBingRankListModel> campUnionMap = new ConcurrentHashMap<>();

        for (CrossTongBingUserData userData : userDataMap.values()) {
            if (isAble(userData) && userData.getZhanGong() > 0) {
                CrossTongBingRank rank = new CrossTongBingRank(String.valueOf(userData.getUserId()), userData.getZhanGong(), userData.getZhanGongTime());
                // 【个人榜】
                userModel.rankChange(rank);
                // 【阵营内个人榜】
                campUserMap.putIfAbsent(userData.getCampId(), new CrossTongBingRankListModel());
                campUserMap.get(userData.getCampId()).rankChange(rank);
            }
        }


        for (CrossTongBingUnionJoin join : unionJoinMap.values()) {
            if (join.getCampId() != 0) {
                // 【阵营内商会榜】
                campUnionMap.putIfAbsent(join.getCampId(), new CrossTongBingRankListModel());
                CrossTongBingRank rank = getUnionZhanGongRank(join);
                campUnionMap.get(join.getCampId()).rankChange(rank);
            }
        }

        userRank = userModel;
        campUserRankMap = campUserMap;
        campUnionRankMap = campUnionMap;
    }

    /**
     * 获取商会战功Rank
     */
    private CrossTongBingRank getUnionZhanGongRank(CrossTongBingUnionJoin join) {
        long unionValue = 0;
        long maxTime = 0;
        for (long memberId : join.getMemberMap().keySet()) {
            CrossTongBingUserData memberData = getUserData(memberId);
            unionValue += memberData.getZhanGong();
            maxTime = Math.max(maxTime, memberData.getZhanGongTime());
        }

        CrossTongBingRank rank = new CrossTongBingRank(join.getUnionUid(), unionValue, maxTime);
        if (join.getTotalAbility() == 0L) {
            join.reCalcAbility();
        }
        rank.setAbility(join.getTotalAbility());

        return rank;
    }

    /**
     * 阵营积分榜单
     */
    public LinkedList<CrossTongBingRank> getCampScoreRankList() {
        Map<Integer, Long> campZhangongMap = new ConcurrentHashMap<>();
        for(CrossTongBingUserData userData : userDataMap.values()){
            long oldValue = campZhangongMap.getOrDefault(userData.getCampId(),0L);
            campZhangongMap.put(userData.getCampId(),oldValue + userData.getZhanGong());
        }
        Map<Integer, Long> campScoreMap = new ConcurrentHashMap<>();
        Map<Integer, Long> updateTimeMap = new ConcurrentHashMap<>();
        for (CrossTongBingCity city : getCrossTongBingData().getCityMap().values()) {
            int campId = city.getCampId();
            int cityId = city.getCityId();

            if (campId == 0) {
                continue;
            }

            // 统计积分
            int cityType = config.getCityConfigMap().get(cityId).getType();
            long addScore = config.getCityTypeConfigMap().get(cityType).getCampPoint();
            long oldScore = campScoreMap.getOrDefault(campId, 0L);
            campScoreMap.put(campId, oldScore + addScore);
            // 存一下时间
            if (city.getOccupyTime() > updateTimeMap.getOrDefault(campId, 0L)) {
                updateTimeMap.put(campId, city.getOccupyTime());
            }
        }

        LinkedList<CrossTongBingRank> rankList = new LinkedList<>();
        for (Map.Entry<Integer, Long> entry : campScoreMap.entrySet()) {
            int campId = entry.getKey();
            String id = String.valueOf(campId);
            CrossTongBingRank rank = new CrossTongBingRank(id, entry.getValue(), updateTimeMap.getOrDefault(entry.getKey(), System.currentTimeMillis()));
            rank.setAbility(campZhangongMap.getOrDefault(campId,0L));
            rankList.add(rank);
        }

        CrossTongBingRankListModel model = new CrossTongBingRankListModel();
        model.setRankList(rankList);

        return model.getRankList();
    }

    // ============================== 战斗 ==============================

    /**
     * 生成战场
     */
    private void createBattlefield(CrossTongBingCity city) {
        CrossTongBingTeamArrive arrive = city.getAtkTeamList().get(0);
        CrossTongBingBattlefield battlefield = new CrossTongBingBattlefield();
        battlefield.setCreateTime(arrive.getTime());
        battlefield.setLastBattleTime(arrive.getTime());
        city.setBattlefield(battlefield);
        city.setTeamRecoveryBeginTime(0); // 城防军恢复停止计时
        teamToBattlefield(city, true);
        addTask(new CrossTongBingBattleTask(this, calcNextBattleTime(city), city.getCityId()));
    }

    /**
     * 生成分战场
     */
    private void createBattlefieldPart(CrossTongBingCity city) {
        CrossTongBingBattlefieldTeam atkTeam = getFirstAtkTeam(city, true);
        CrossTongBingBattlefieldTeam defTeam = getFirstDefTeam(city, true);

        CrossTongBingBattlefield battlefield = city.getBattlefield();
        int partId = battlefield.getMaxPartId() + 1;
        battlefield.setMaxPartId(partId);

        CrossTongBingBattlefieldPart part = new CrossTongBingBattlefieldPart();
        part.setPartId(partId);
        part.getAtkList().add(atkTeam);
        part.getDefList().add(defTeam);

        battlefield.getPartMap().put(partId, part);
    }

    /**
     * 该城市下次战斗时间
     */
    public long calcNextBattleTime(CrossTongBingCity city) {
        CrossTongBingBattlefield battlefield = city.getBattlefield();
        if (battlefield == null) {
            return 0;
        }

        // 上次战斗时间
        long lastBattleTime = battlefield.getLastBattleTime();

        // 第一次战斗等一分钟
        if (battlefield.getBattledNum() == 0) {
            return lastBattleTime + config.getBattlePrepareTime();
        }

        long battleCostTime = config.getBattleCostTime();
        // 有战鼓，就减为1秒（下一场生效，所以判断UseDrumTime）
        if (battlefield.getDrumTimes() > 0 && battlefield.getUseDrumTime() < lastBattleTime) {
            battleCostTime = GoodsMgr.getGoodsInfo(config.getZhanGuItem()).getParamList().get(1).intValue() * DateHelper.SECOND_MILLIONS;
        }

        return battlefield.getLastBattleTime() + battleCostTime;
    }

    /**
     * 队伍进战场
     */
    public void teamToBattlefield(CrossTongBingCity city, boolean needSync) {

        // 记录有变化的战场，用于同步
        Set<CrossTongBingBattlefieldPart> changePartSet = new HashSet<>();

        CrossTongBingBattlefield battlefield = city.getBattlefield();

        // 【空战场先填人】
        for (CrossTongBingBattlefieldPart part : battlefield.getPartMap().values()) {
            if (part.getAtkList().isEmpty()) {
                CrossTongBingBattlefieldTeam team = getFirstAtkTeam(city, true);
                if (team != null) {
                    part.getAtkList().addLast(team);
                    changePartSet.add(part);
                }
            }
            if (part.getDefList().isEmpty()) {
                CrossTongBingBattlefieldTeam team = getFirstDefTeam(city, true);
                if (team != null) {
                    part.getDefList().addLast(team);
                    changePartSet.add(part);
                }
            }
        }

        // 【开新战场】
        for (int i = 0; i < 999; i++) {
            boolean hasAtk = getFirstAtkTeam(city, false) != null;
            boolean hasDef = getFirstDefTeam(city, false) != null;
            if (!hasAtk || !hasDef) {
                break; // 不满足两边都有队伍，无法开新战场
            }
            int curPartNum = battlefield.getPartMap().size();
            int maxPartNum = getMaxBattlefieldPartNum(city);
            if (curPartNum < maxPartNum) {
                createBattlefieldPart(city); // 没到战场最大数量，可以开新战场
                changePartSet.add(battlefield.getPartMap().get(battlefield.getMaxPartId()));
            }
        }

        Map<Integer, CrossTongBingBattlefieldPart> partMap = battlefield.getPartMap();

        // 【其余进攻方填进去】
        for (int i = 0; i < 999; i++) {
            // 没人了就不用加了
            if (getFirstAtkTeam(city, false) == null) {
                break;
            }
            // 找最小人数的战场
            CrossTongBingBattlefieldPart part = null;
            for (CrossTongBingBattlefieldPart tmp : partMap.values()) {
                if (!battlefieldPartIsFull(tmp, false) &&
                        (part == null || tmp.getAtkList().size() < part.getAtkList().size())) {
                    part = tmp;
                }
            }
            if (part != null) {
                part.getAtkList().add(getFirstAtkTeam(city, true));
                changePartSet.add(part);
            } else {
                break;
            }
        }

        // 【其余防守方填进去】
        for (int i = 0; i < 999; i++) {
            // 没人了就不用加了
            if (getFirstDefTeam(city, false) == null) {
                break;
            }
            // 找最小人数的战场
            CrossTongBingBattlefieldPart part = null;
            for (CrossTongBingBattlefieldPart tmp : partMap.values()) {
                if (!battlefieldPartIsFull(tmp, true) &&
                        (part == null || tmp.getDefList().size() < part.getDefList().size())) {
                    part = tmp;
                }
            }
            if (part != null) {
                part.getDefList().add(getFirstDefTeam(city, true));
                changePartSet.add(part);
            } else {
                break;
            }
        }

        if (needSync) {
            // 同步队伍进入战场
            for (CrossTongBingBattlefieldPart part : changePartSet) {
                syncTeamEnterBattlefield(city, battlefield, part);
            }
        }
    }

    /**
     * 战场是否填满
     */
    private boolean battlefieldPartIsFull(CrossTongBingBattlefieldPart part, boolean isDef) {
        int count = isDef ? part.getDefList().size() : part.getAtkList().size();
        int deadCount = isDef ? part.getDefDeadCount() : part.getAtkDeadCount();
        boolean win = part.getWinType() == 1 && isDef || part.getWinType() == 2 && !isDef;

        /*
         *      战场举例：
         *
         *          2   3
         *      4   5
         *
         *      firstNo = 2
         *      lineFirstNo = 1
         *      endNo = 5
         *      endNo - lineFirstNo + 1 = x    ===>    战场内队伍已经占了x个格子
         */

        // 找第一行最左边的编号
        int firstNo = deadCount + (win ? 1 : 0) + 1;
        int lineFirstNo = (firstNo + 2) / 3 * 3 - 2;

        // 队列最后一个的编号
        int endNo = deadCount + count;

        return endNo - lineFirstNo + 1 >= 6;
    }

    /**
     * 获取第一个进攻队伍
     */
    public CrossTongBingBattlefieldTeam getFirstAtkTeam(CrossTongBingCity city, boolean remove) {
        if (city.getAtkTeamList().size() <= 0) {
            return null;
        }
        CrossTongBingTeamArrive arrive;
        if (remove) {
            arrive = city.getAtkTeamList().removeFirst();
        } else {
            arrive = city.getAtkTeamList().getFirst();
        }
        return new CrossTongBingBattlefieldTeam(arrive);
    }

    /**
     * 获取第一个防守队伍
     */
    public CrossTongBingBattlefieldTeam getFirstDefTeam(CrossTongBingCity city, boolean remove) {
        // 优先出战基础城防守军
        if (getSoldierTeamCount(city) > 0) {
            if (city.getTeamNum() > 0) {
                if (remove) {
                    city.setTeamNum(city.getTeamNum() - 1);
                }
                // 普通城防兵
                return new CrossTongBingBattlefieldTeam(city.getTeamSoldierNum(), eTongBingSoldierType.COMMON_SOLDIER.getValue());
            } else {
                if (remove) {
                    city.setExtraTeamNum(city.getExtraTeamNum() - 1);
                }
                // 额外城防兵
                return new CrossTongBingBattlefieldTeam(city.getTeamSoldierNum(), eTongBingSoldierType.EXTRA_SOLDIER.getValue());
            }
        }

        if (city.getDefTeamList().size() <= 0) {
            return null;
        }
        CrossTongBingTeamArrive arrive;
        if (remove) {
            arrive = city.getDefTeamList().removeFirst();
        } else {
            arrive = city.getDefTeamList().getFirst();
        }
        return new CrossTongBingBattlefieldTeam(arrive);
    }

    public void teamReturnWaitList(CrossTongBingCity city, CrossTongBingBattlefieldPart part) {
        // 进攻方
        LinkedList<CrossTongBingBattlefieldTeam> atkList = part.getAtkList();
        while (atkList.size() > 0) {
            CrossTongBingBattlefieldTeam team = atkList.removeLast();
            if (team.getTeam() != null) {
                city.getAtkTeamList().addFirst(team.getTeam());
            }
        }
        // 防守方
        LinkedList<CrossTongBingBattlefieldTeam> defList = part.getDefList();
        while (defList.size() > 0) {
            CrossTongBingBattlefieldTeam team = defList.removeLast();
            if (team.getTeam() != null) {
                city.getDefTeamList().addFirst(team.getTeam());
            } else if (team.getSoldierNum() > 0) {
                if (team.getSoldierType() == eTongBingSoldierType.COMMON_SOLDIER.getValue()) {
                    city.setTeamNum(city.getTeamNum() + 1);
                } else if (team.getSoldierType() == eTongBingSoldierType.EXTRA_SOLDIER.getValue()) {
                    city.setExtraTeamNum(city.getExtraTeamNum() + 1);
                }
            }
        }
    }

    /**
     * 最大战场数
     */
    public int getMaxBattlefieldPartNum(CrossTongBingCity city) {
        int atkNum = city.getAtkTeamList().size();
        int defNum = city.getDefTeamList().size() + getSoldierTeamCount(city);
        CrossTongBingBattlefield battlefield = city.getBattlefield();
        if (battlefield != null) {
            for (CrossTongBingBattlefieldPart part : battlefield.getPartMap().values()) {
                atkNum += part.getAtkList().size();
                defNum += part.getDefList().size();
            }
        }

        int param = config.getBattlefieldPartParam();

        int num1 = atkNum / param;
        int num2 = defNum / param;

        int result = Math.min(num1, num2);

        if (result <= 0) {
            result = 1;
        }
        return result;
    }

    /**
     * 处理战斗
     */
    public CrossTongBingBattleResult handleBattle(CrossTongBingCity city,
                                                  CrossTongBingBattlefield battlefield,
                                                  CrossTongBingBattlefieldPart part,
                                                  long battleTime) {
        if (part.getAtkList().isEmpty() || part.getDefList().isEmpty()) {
            logger.error("battle error !!!!! cityId {} partId {}, see db log...", city.getCityId(), part.getPartId());
            return null;
        }
        CrossTongBingBattlefieldTeam atk = part.getAtkList().getFirst();
        CrossTongBingBattlefieldTeam def = part.getDefList().getFirst();

        // 阵营ID
        int atkCampId = getUserData(atk.getTeam().getUserId()).getCampId();
        int defCampId = city.getCampId();

        // 获取士兵数
        long defCount = def.getTeam() == null ? def.getSoldierNum() : getTeamByArrive(def.getTeam()).getSoldierNum();
        long atkCount = getTeamByArrive(atk.getTeam()).getSoldierNum();

        // 计算守军buff
        TongBingCityConfig cityConfig = config.getCityConfigMap().get(city.getCityId());
        TongBingCityTypeConfig cityTypeConfig = config.getCityTypeConfigMap().get(cityConfig.getType());
        int defAddition = cityTypeConfig.getAddition() + cityConfig.getSpecialAdditionMap().getOrDefault(defCampId, 0);
        int atkAddition = cityConfig.getSpecialAdditionMap().getOrDefault(atkCampId, 0);
        long defCount2 = defCount * (1000 + defAddition) / 1000;
        long atkCount2 = atkCount * (1000 + atkAddition) / 1000;

        // 判断胜利方
        int winType = defCount2 == atkCount2 ? 0 : (defCount2 > atkCount2 ? 1 : 2);

        // 损兵比例
        BigDecimal defCostPercent = winType != 1 ? BigDecimal.ONE : calcBattleCostPercent(defCount2, atkCount2);
        BigDecimal atkCostPercent = winType != 2 ? BigDecimal.ONE : calcBattleCostPercent(atkCount2, defCount2);

        // 主城战斗时，守军必胜
        if (cityConfig.getType() == 1) {
            winType = 1;
            defCostPercent = BigDecimal.ZERO;
            atkCostPercent = BigDecimal.ONE;
        }

        // 计算伤亡
        long defCost = BigDecimal.valueOf(defCount).multiply(defCostPercent).setScale(0, BigDecimal.ROUND_DOWN).longValue();
        long atkCost = BigDecimal.valueOf(atkCount).multiply(atkCostPercent).setScale(0, BigDecimal.ROUND_DOWN).longValue();

        // 扣伤亡 + 加伤兵
        if (def.getTeam() != null) {
            CrossTongBingTeam defTeam = getTeamByArrive(def.getTeam());
            defTeam.setSoldierNum(defTeam.getSoldierNum() - defCost);
            long defWoundedNum = calcChangeWoundedSoldierNum(defCost);
            addBarracksWoundedSoldier(defTeam.getUserId(), defWoundedNum);
            getUserData(def.getTeam().getUserId()).setUpdateOption();
        } else {
            def.setSoldierNum(def.getSoldierNum() - defCost);
        }
        CrossTongBingTeam atkTeam = getTeamByArrive(atk.getTeam());
        atkTeam.setSoldierNum(atkTeam.getSoldierNum() - atkCost);
        long atkWoundedNum = calcChangeWoundedSoldierNum(atkCost);
        addBarracksWoundedSoldier(atkTeam.getUserId(), atkWoundedNum);
        getUserData(atk.getTeam().getUserId()).setUpdateOption();

        // 失败方回城
        if (winType != 1 && def.getTeam() != null) {
            CrossTongBingTeam team = getTeamByArrive(def.getTeam());
            team.setRouteList(Collections.singletonList(getBirthCityId(getUserData(team.getUserId()).getCampId())));
            forceArriveNextCity(team, battleTime);
            handleTargetCity(team);
        }
        if (winType != 2) {
            atkTeam.setRouteList(Collections.singletonList(getBirthCityId(getUserData(atkTeam.getUserId()).getCampId())));
            forceArriveNextCity(atkTeam, battleTime);
            handleTargetCity(atkTeam);
        }

        // 加战功
        long defZhanGong = calcZhanGong(defCost, defCount);
        if (def.getTeam() != null) {
            addZhanGong(def.getTeam().getUserId(), defZhanGong, battleTime);
        }
        long atkZhanGong = calcZhanGong(atkCost, atkCount);
        addZhanGong(atk.getTeam().getUserId(), atkZhanGong, battleTime);

        // 连胜
        int defWinTimes = def.getTeam() == null ? 0 : battlefield.getWinTimes().getOrDefault(getTeamKey(def.getTeam()), 0);
        int atkWinTimes = battlefield.getWinTimes().getOrDefault(getTeamKey(atk.getTeam()), 0);
        if (winType == 1) {
            if (def.getTeam() != null) battlefield.getWinTimes().put(getTeamKey(def.getTeam()), ++defWinTimes);
            battlefield.getWinTimes().remove(getTeamKey(atk.getTeam()));
        } else if (winType == 2) {
            if (def.getTeam() != null) battlefield.getWinTimes().remove(getTeamKey(def.getTeam()));
            if (def.getTeam() != null)
                battlefield.getWinTimes().put(getTeamKey(atk.getTeam()), ++atkWinTimes); // 打城防军不算连胜
        } else {
            if (def.getTeam() != null) battlefield.getWinTimes().remove(getTeamKey(def.getTeam()));
            battlefield.getWinTimes().remove(getTeamKey(atk.getTeam()));
        }
        // 连胜公告
        checkContinueWinReport(city.getCityId(), winType, battleTime,
                def, defWinTimes,
                atk, atkWinTimes);

        // 战场排行
        if (def.getTeam() != null) {
            battlefield.rankChange(def.getTeam().getUserId(), defZhanGong, atkCost);
        }
        battlefield.rankChange(atkTeam.getUserId(), atkZhanGong, defCost);

        // 记录战斗结果
        if (winType != 1) {
            part.getDefList().removeFirst();
            part.setDefDeadCount(part.getDefDeadCount() + 1);
        }
        if (winType != 2) {
            part.getAtkList().removeFirst();
            part.setAtkDeadCount(part.getAtkDeadCount() + 1);
        }
        part.setWinType(winType);

        // 记录个战报
        CrossTongBingUserWarReport userReport = new CrossTongBingUserWarReport();
        if (def.getTeam() == null) {
            userReport.setUserIdDef(0);
            userReport.setDefPatronsId(0);
        } else {
            CrossTongBingTeam defTeam = getTeamByArrive(def.getTeam());
            userReport.setUserIdDef(defTeam.getUserId());
            userReport.setDefPatronsId(defTeam.getPatronsList().isEmpty() ? 0 : defTeam.getPatronsList().get(0));
        }
        userReport.setActivityId(config.getActivityInfo().getActivityId());
        userReport.setGroupId(groupId);
        userReport.setUserIdAct(atkTeam.getUserId());
        userReport.setActPatronsId(atkTeam.getPatronsList().isEmpty() ? 0 : atkTeam.getPatronsList().get(0));
        userReport.setDefSoldierNum(defCount);
        userReport.setActSoldierNum(atkCount);
        userReport.setDefAddition(defAddition);
        userReport.setActAddition(atkAddition);
        userReport.setDefZhanGong(defZhanGong);
        userReport.setActZhanGong(atkZhanGong);
        userReport.setDefDieSoldierNum(defCost);
        userReport.setActDieSoldierNum(atkCost);
        userReport.setWinUserId(winType);
        userReport.setTime(battleTime);
        userReport.setCityId(city.getCityId());
        addUserWarReport(userReport);

        // 返回结果
        return new CrossTongBingBattleResult(
                part, battlefield, city,
                winType,
                def, defCost, defZhanGong,
                atk, atkCost, atkZhanGong,
                winType == 1 ? defWinTimes : winType == 2 ? atkWinTimes : 0
        );
    }

    /**
     * 处理战略目标操作
     */
    private void handleTargetCity(CrossTongBingTeam team) {
        if (team.getTargetCityId() != 0) {
            if (!moveToTargetCity(team)) {
                // 如果兵力不足或者无法前往城池，取消战略目标
                team.setTargetCityId(0);
                // 队伍同步
                syncTeamToSelf(team.getUserId(), Collections.singletonList(team));
            }
        }
    }

    /**
     * 补满兵自动前往战略目标
     */
    private boolean moveToTargetCity(CrossTongBingTeam team) {
        CrossTongBingUserData userData = getUserData(team.getUserId());
        if (!isAble(userData)) {
            return false;
        }
        // 判断预备兵是否足够
        if (team.getMaxSoldier() > userData.getBarracks().getReadyNum()) {
            return false;
        }
        // 预备兵扣除
        userData.getBarracks().setReadyNum(userData.getBarracks().getReadyNum() - team.getMaxSoldier());
        // 补满兵力
        team.setSoldierNum(team.getMaxSoldier());
        userData.setUpdateOption();
        // 前往战略目标
        return moveToCity(team, team.getTargetCityId()) == 0;
    }

    /**
     * 检查连胜公告
     */
    private void checkContinueWinReport(int cityId, int winType, long time,
                                        CrossTongBingBattlefieldTeam defTeam, int defWinTimes,
                                        CrossTongBingBattlefieldTeam atkTeam, int atkWinTimes) {
        int num = config.getContinueWinReportTimes();

        // 拿昵称
        String defNickname;
        if (defTeam.getTeam() != null) {
            UserBaseInfo defInfo = CrossUserMgr.getUserBaseInfo(defTeam.getTeam().getUserId());
            defNickname = defInfo == null ? "" : defInfo.getNickName();
        } else {
            defNickname = config.getCityDefTeamNameKey();
        }
        UserBaseInfo atkInfo = CrossUserMgr.getUserBaseInfo(atkTeam.getTeam().getUserId());
        String atkNickname = atkInfo == null ? "" : atkInfo.getNickName();

        // 看连胜
        if (winType == 1 && defWinTimes >= num) {
            addSystemChat(eMessageType.TongBingBattlefieldWinChat, cityId,
                    1 + GameConfig.TALK_SEPARATOR + defNickname + GameConfig.TALK_SEPARATOR + defWinTimes + GameConfig.TALK_SEPARATOR + atkNickname + GameConfig.TALK_SEPARATOR + atkWinTimes
                    , time);
        }
        if (winType == 2 && atkWinTimes >= num) {
            addSystemChat(eMessageType.TongBingBattlefieldWinChat, cityId,
                    1 + GameConfig.TALK_SEPARATOR + atkNickname + GameConfig.TALK_SEPARATOR + atkWinTimes + GameConfig.TALK_SEPARATOR + defNickname + GameConfig.TALK_SEPARATOR + defWinTimes
                    , time);
        }
        // 看终结连胜
        if (winType != 1 && defWinTimes >= num) {
            addSystemChat(eMessageType.TongBingBattlefieldWinChat, cityId,
                    2 + GameConfig.TALK_SEPARATOR + atkNickname + GameConfig.TALK_SEPARATOR + atkWinTimes + GameConfig.TALK_SEPARATOR + defNickname + GameConfig.TALK_SEPARATOR + defWinTimes
                    , time);
        }
        if (winType != 2 && atkWinTimes >= num) {
            addSystemChat(eMessageType.TongBingBattlefieldWinChat, cityId,
                    2 + GameConfig.TALK_SEPARATOR + defNickname + GameConfig.TALK_SEPARATOR + defWinTimes + GameConfig.TALK_SEPARATOR + atkNickname + GameConfig.TALK_SEPARATOR + atkWinTimes
                    , time);
        }
    }

    /**
     * 加战功值
     */
    private void addZhanGong(long userId, long zhanGong, long battleTime) {

        // 领奖期不能加战功
        if (battleTime >= config.getActivityInfo().getEndTime() * 1000) {
            return;
        }

        CrossTongBingUserData userData = getUserData(userId);
        String userRankId = String.valueOf(userId);

        // （之前是否发光）
        int lightBeforeRank = Integer.MAX_VALUE;
        if (userData.getZhanGong() >= config.getLightNeedZhanGong()) {
            int rankNum = 0;
            for (CrossTongBingRank tmp : userRank.getRankList()) {
                if (++rankNum > config.getLightRankNum()) {
                    break;
                }
                if (Objects.equals(tmp.getId(), userRankId)) {
                    lightBeforeRank = rankNum;
                    break;
                }
            }
        }

        // 加战功
        userData.setZhanGong(userData.getZhanGong() + zhanGong);
        userData.setZhanGongTime(battleTime);

        // 个人榜
        CrossTongBingRank rank = new CrossTongBingRank(String.valueOf(userId), userData.getZhanGong(), System.currentTimeMillis());
        userRank.rankChange(rank);

        // 阵营内个人榜
        campUserRankMap.putIfAbsent(userData.getCampId(), new CrossTongBingRankListModel());
        campUserRankMap.get(userData.getCampId()).rankChange(rank);

        // 阵营内商会榜
        CrossTongBingUnionJoin join = unionJoinMap.get(userData.getUnionUid());
        if (join != null) {
            long unionValue = 0;
            for (long memberId : join.getMemberMap().keySet()) {
                CrossTongBingUserData memberData = getUserData(memberId);
                unionValue += memberData.getZhanGong();
            }

            CrossTongBingRank unionRank = new CrossTongBingRank(join.getUnionUid(), unionValue, System.currentTimeMillis());
            if (join.getTotalAbility() == 0L) {
                join.reCalcAbility();
            }
            unionRank.setAbility(join.getTotalAbility());

            campUnionRankMap.putIfAbsent(userData.getCampId(), new CrossTongBingRankListModel());
            campUnionRankMap.get(userData.getCampId()).rankChange(unionRank);
        }

        // 之后是否发光
        int lightAfterRank = Integer.MAX_VALUE;
        if (userData.getZhanGong() >= config.getLightNeedZhanGong()) {
            int rankNum = 0;
            for (CrossTongBingRank tmp : userRank.getRankList()) {
                if (++rankNum > config.getLightRankNum()) {
                    break;
                }
                if (Objects.equals(tmp.getId(), userRankId)) {
                    lightAfterRank = rankNum;
                    break;
                }
            }
        }

        // 发光
        if (lightAfterRank <= config.getLightRankNum()) {
            if (lightBeforeRank > config.getLightRankNum()) { // 新发光
                syncLight();
            } else if (lightAfterRank != lightBeforeRank) { // 旧发光，但光变了
                syncLight();
            }
        }

        // 通知游戏服更新condition成就
        notifyConditionChange(userId, eGamePlayerEventType.TongBingZhanGong, userData.getZhanGong());
        // 同步用户数据
        syncUserDataToGameServer(userData);
    }

    /**
     * 计算转换伤兵数量
     */
    private long calcChangeWoundedSoldierNum(long deadNum) {
        return deadNum * config.getWoundedSoldierPercent() / 1000;
    }

    /**
     * 计算战功
     */
    private long calcZhanGong(long soldierCostNum, long mySoldierNum) {
        BigDecimal param1 = BigDecimal.valueOf(config.getZhanGongParam().get(0));
        BigDecimal param2 = BigDecimal.valueOf(config.getZhanGongParam().get(1));
        BigDecimal param3 = BigDecimal.valueOf(config.getZhanGongParam().get(2));
        BigDecimal param4 = BigDecimal.valueOf(config.getZhanGongParam().get(3));
        BigDecimal costNum = BigDecimal.valueOf(soldierCostNum);
        BigDecimal one = BigDecimal.ONE;
        BigDecimal myNum = BigDecimal.valueOf(mySoldierNum);

        BigDecimal part2 = myNum.divide(param4, 8, BigDecimal.ROUND_DOWN); // 我方兵数/参数4
        BigDecimal part3 = param1.divide(param2, 8, BigDecimal.ROUND_DOWN); // 参数1/参数2

        BigDecimal value = one
                .add(part2.multiply(part3))
                .multiply(costNum)
                .multiply(param3);

        return value.setScale(0, BigDecimal.ROUND_DOWN).longValue();
    }

    /**
     * 计算损兵比例
     */
    private BigDecimal calcBattleCostPercent(long myCount, long enemyCount) {
        // 容错
        myCount = myCount <= 0 ? 1 : myCount;
        enemyCount = enemyCount <= 0 ? 1 : enemyCount;

        BigDecimal param1 = BigDecimal.valueOf(config.getBattleParam().get(0));
        BigDecimal param2 = BigDecimal.valueOf(config.getBattleParam().get(1));
        BigDecimal param3 = BigDecimal.valueOf(config.getBattleParam().get(2));
        BigDecimal param4 = BigDecimal.valueOf(config.getBattleParam().get(3));
        BigDecimal param5 = BigDecimal.valueOf(config.getBattleParam().get(4));
        BigDecimal param6 = BigDecimal.valueOf(config.getBattleParam().get(5));
        BigDecimal param7 = BigDecimal.valueOf(config.getBattleParam().get(6));

        // 敌方兵数/我方兵数
        BigDecimal partSoldier = BigDecimal.valueOf(enemyCount).divide(BigDecimal.valueOf(myCount), 8, BigDecimal.ROUND_DOWN);
        // 参数1/参数2
        BigDecimal part12 = param1.divide(param2, 8, BigDecimal.ROUND_DOWN);
        // 参数3/参数4
        BigDecimal part34 = param3.divide(param4, 8, BigDecimal.ROUND_DOWN);
        // 参数5/参数6
        BigDecimal part56 = param5.divide(param6, 8, BigDecimal.ROUND_DOWN);

        BigDecimal partSoldier12 = BigDecimal.valueOf(Math.pow(partSoldier.doubleValue(), part12.doubleValue())).setScale(8, BigDecimal.ROUND_DOWN);

        BigDecimal result = partSoldier12.multiply(part34)
                .add(partSoldier.multiply(part56))
                .add(param7)
                .multiply(partSoldier)
                .setScale(8, BigDecimal.ROUND_DOWN);

        // 不能超过1
        if (result.compareTo(BigDecimal.ONE) > 0) {
            result = BigDecimal.ONE;
        }

        return result;
    }

    /**
     * 记录城池战报
     */
    public CrossTongBingCityWarReport recordBattleReport(CrossTongBingCity city, long time) {

        // 城池战报数据准备
        int defCampId = city.getCampId();
        CrossTongBingBattlefield battlefield = city.getBattlefield();
        LinkedList<CrossTongBingBattlefieldRank> rankList = battlefield.getBattlefieldRankNewList();
        LinkedList<CrossTongBingBattlefieldRank> atkRank = new LinkedList<>();
        LinkedList<CrossTongBingBattlefieldRank> defRank = new LinkedList<>();
        Set<Integer> atkCampSet = new HashSet<>();
        for (CrossTongBingBattlefieldRank rank : rankList) {
            int campId = getUserData(rank.getUserId()).getCampId();
            if (defCampId == campId) {
                if (defRank.size() < 3) {
                    defRank.add(rank);
                }
            } else {
                if (atkRank.size() < 3) {
                    atkRank.add(rank);
                }
                atkCampSet.add(campId);
            }
        }

        boolean defWin = city.getAtkTeamList().isEmpty();
        int winCampId = defCampId;
        if (!defWin) {
            CrossTongBingTeamArrive first = city.getAtkTeamList().getFirst();
            winCampId = getUserData(first.getUserId()).getCampId();
        }

        // 加城池战报
        CrossTongBingCityWarReport report = new CrossTongBingCityWarReport();
        report.setActivityId(config.getActivityInfo().getActivityId());
        report.setGroupId(groupId);
        report.setAtkCampId(new ArrayList<>(atkCampSet));
        report.setDefCampId(defCampId);
        report.setType(defWin ? 2 : 1);
        report.setCityId(city.getCityId());
        report.setTime(battlefield.getLastBattleTime());
        report.setAtkRankList(atkRank);
        report.setDefRankList(defRank);
        report.setWinCampId(winCampId);
        addCityWarReport(report);

        return report;
    }

    /**
     * 处理城池战斗结束
     */
    public void handleBattleFinish(CrossTongBingCity city, long time) {

        int defCampId = city.getCampId();

        CrossTongBingBattlefield battlefield = city.getBattlefield();

        TongBingCityConfig cityConfig = config.getCityConfigMap().get(city.getCityId());
        TongBingCityTypeConfig typeConfig = config.getCityTypeConfigMap().get(cityConfig.getType());

        boolean defWin = city.getAtkTeamList().isEmpty();
        if (!defWin) {
            // 占领了
            CrossTongBingTeamArrive first = city.getAtkTeamList().getFirst();
            int campId = getUserData(first.getUserId()).getCampId();

            // 同阵营队伍抽出来
            LinkedList<CrossTongBingTeamArrive> atkList = new LinkedList<>();
            LinkedList<CrossTongBingTeamArrive> defList = new LinkedList<>();
            for (CrossTongBingTeamArrive arrive : new LinkedList<>(city.getAtkTeamList())) {
                if (campId == getUserData(arrive.getUserId()).getCampId()) {
                    defList.add(arrive);
                } else {
                    atkList.add(arrive);
                }
            }
            atkList.sort(Comparator.comparing(CrossTongBingTeamArrive::getTime));
            defList.sort(Comparator.comparing(CrossTongBingTeamArrive::getTime));

            // 拿buff
            CrossTongBingCampBuff oldBuff = null;
            CrossTongBingCampBuff newBuff = null;
            if (typeConfig.getBuffEffect() != 0) {
                oldBuff = city.getCampId() == 0 ? null : new CrossTongBingCampBuff(city.getCampId(), city.getOccupyTime(), city.getCityId(), typeConfig.getBuffEffect(), time);
                newBuff = new CrossTongBingCampBuff(campId, time, city.getCityId(), typeConfig.getBuffEffect());
            }

            // 先计算buff
            if (oldBuff != null) {
                beforeEndBuff(oldBuff);
            }
            if (newBuff != null) {
                beforeBeginBuff(newBuff);
            }

            //领取一下城池收货
            makeAllCityCollect(city.getCampId(), time);

            // 修改城池数据
            city.setCampId(campId);
            city.setCollectionTime(time);
            city.setOccupyTime(time);
            city.setDefTeamList(defList);
            city.setAtkTeamList(atkList);

            // 再计算buff
            if (oldBuff != null) {
                afterEndBuff(oldBuff);
            }
            if (newBuff != null) {
                afterBeginBuff(newBuff);
            }

            // 如果有进攻方，准继续战斗
            if (city.getAtkTeamList().size() > 0) {
                addTask(new CrossTongBingBattleTask(this, calcNextBattleTime(city), city.getCityId()));
            }
            // 被占领后开始恢复额外守军
            city.setLastRecoveryExtraTeamTime(time);
            addTask(new CrossTongBingRecoveryExtraTeamTask(this, getNextRecoveryExtraTeamTime(city), city.getCityId()));
        }

        // 城池守军恢复时间
        city.setTeamRecoveryBeginTime(time);
        addTask(new CrossTongBingCityTeamRecoveryTask(this, config.getCityTeamRecoveryTime() + city.getTeamRecoveryBeginTime(), city.getCityId()));

        // 加聊天
        addSystemChat(eMessageType.TongBingPublicBattleChat, config.getActivityInfo().getActivityId(),
                (!defWin ? 1 : 2) + GameConfig.TALK_SEPARATOR + city.getCityId() + GameConfig.TALK_SEPARATOR + city.getCampId() + GameConfig.TALK_SEPARATOR + city.getBattledNum() + GameConfig.TALK_SEPARATOR + defCampId,
                time);

        // 同步顶部横幅
        syncTopMsg(city.getCampId(), city.getCityId(), defWin ? 2 : 1, battlefield, defCampId);
    }

    /**
     * 未战斗直接攻取城池
     */
    public void handleNoBattleOccupy(CrossTongBingCity city, long time) {

        TongBingCityConfig cityConfig = config.getCityConfigMap().get(city.getCityId());
        TongBingCityTypeConfig typeConfig = config.getCityTypeConfigMap().get(cityConfig.getType());

        int atkCampId = getUserData(city.getAtkTeamList().getFirst().getUserId()).getCampId();
        int defCampId = city.getCampId();
        int winCampId = atkCampId;
        boolean defWin = false;

        // 【加城池战报】recordBattleReport

        CrossTongBingCityWarReport report = new CrossTongBingCityWarReport();
        report.setActivityId(config.getActivityInfo().getActivityId());
        report.setGroupId(groupId);
        report.setAtkCampId(Collections.singletonList(atkCampId));
        report.setDefCampId(defCampId);
        report.setType(defWin ? 2 : 1);
        report.setCityId(city.getCityId());
        report.setTime(time);
        report.setAtkRankList(new LinkedList<>());
        report.setDefRankList(new LinkedList<>());
        report.setWinCampId(winCampId);
        addCityWarReport(report);

        // 【处理战斗结束】handleBattleFinish

        // 同阵营队伍抽出来
        LinkedList<CrossTongBingTeamArrive> atkList = new LinkedList<>();
        LinkedList<CrossTongBingTeamArrive> defList = new LinkedList<>();
        for (CrossTongBingTeamArrive arrive : new LinkedList<>(city.getAtkTeamList())) {
            if (winCampId == getUserData(arrive.getUserId()).getCampId()) {
                defList.add(arrive);
            } else {
                atkList.add(arrive);
            }
        }
        atkList.sort(Comparator.comparing(CrossTongBingTeamArrive::getTime));
        defList.sort(Comparator.comparing(CrossTongBingTeamArrive::getTime));

        // 拿buff
        CrossTongBingCampBuff oldBuff = null;
        CrossTongBingCampBuff newBuff = null;
        if (typeConfig.getBuffEffect() != 0) {
            oldBuff = city.getCampId() == 0 ? null : new CrossTongBingCampBuff(city.getCampId(), city.getOccupyTime(), city.getCityId(), typeConfig.getBuffEffect(), time);
            newBuff = new CrossTongBingCampBuff(winCampId, time, city.getCityId(), typeConfig.getBuffEffect());
        }

        // 先计算buff
        if (oldBuff != null) {
            beforeEndBuff(oldBuff);
        }
        if (newBuff != null) {
            beforeBeginBuff(newBuff);
        }

        //领取一下城池收货
        makeAllCityCollect(city.getCampId(), time);

        // 修改城池数据
        city.setCampId(winCampId);
        city.setCollectionTime(time);
        city.setOccupyTime(time);
        city.setDefTeamList(defList);
        city.setAtkTeamList(atkList);

        // 再计算buff
        if (oldBuff != null) {
            afterEndBuff(oldBuff);
        }
        if (newBuff != null) {
            afterBeginBuff(newBuff);
        }

        // 如果有进攻方，准继续战斗
        if (city.getAtkTeamList().size() > 0) {
            addTask(new CrossTongBingBattleTask(this, calcNextBattleTime(city), city.getCityId()));
        }

        // 城池守军恢复时间
        city.setTeamRecoveryBeginTime(time);

        addTask(new CrossTongBingCityTeamRecoveryTask(this, config.getCityTeamRecoveryTime() + city.getTeamRecoveryBeginTime(), city.getCityId()));

        // 被占领后开始恢复额外守军
        city.setLastRecoveryExtraTeamTime(time);
        addTask(new CrossTongBingRecoveryExtraTeamTask(this, getNextRecoveryExtraTeamTime(city), city.getCityId()));

        // 加聊天
        addSystemChat(eMessageType.TongBingPublicBattleChat, config.getActivityInfo().getActivityId(),
                (!defWin ? 1 : 2) + GameConfig.TALK_SEPARATOR + city.getCityId() + GameConfig.TALK_SEPARATOR + city.getCampId() + GameConfig.TALK_SEPARATOR + 0 + GameConfig.TALK_SEPARATOR + defCampId,
                time);

        // 同步顶部横幅
        syncTopMsg(city.getCampId(), city.getCityId(), defWin ? 2 : 1, null, defCampId);
    }

    /**
     * 移除空战场
     *
     * @return 清空战场的ID
     */
    public List<Integer> removeEmptyBattlefield(CrossTongBingCity city) {
        List<Integer> logRemove = new ArrayList<>();
        CrossTongBingBattlefield battlefield = city.getBattlefield();
        Map<Integer, CrossTongBingBattlefieldPart> partMap = battlefield.getPartMap();
        for (Map.Entry<Integer, CrossTongBingBattlefieldPart> entry : partMap.entrySet()) {
            CrossTongBingBattlefieldPart part = entry.getValue();
            LinkedList<CrossTongBingBattlefieldTeam> atkList = part.getAtkList();
            LinkedList<CrossTongBingBattlefieldTeam> defList = part.getDefList();
            if (atkList.isEmpty() || defList.isEmpty()) {
                // 队伍回到排队里
                teamReturnWaitList(city, part);
                // 移除空战场
                partMap.remove(entry.getKey());
                // 记录ID
                logRemove.add(part.getPartId());
            }
        }
        return logRemove;
    }

    private int getBattleField(long userId, TongBingProto.TongBingGetBattlefieldReqMsg reqMsg) {
        int cityId = reqMsg.getCityId();
        CrossTongBingCity city = getCrossTongBingData().getCityMap().get(cityId);
        if (city == null) {
            return GameErrorCode.E_TONG_BING_PARAM_ERR;
        }
        CrossTongBingBattlefield battlefield = city.getBattlefield();
        if (battlefield == null) {
            return GameErrorCode.E_TONG_BING_NO_BATTLE;
        }
        CrossTongBingBattlefieldPart part = null;
        if (reqMsg.hasPartId()) {
            part = battlefield.getPartMap().get(reqMsg.getPartId());
        } else {
            for (CrossTongBingBattlefieldPart tmp : battlefield.getPartMap().values()) {
                part = tmp;
                break;
            }
        }
        if (part == null) {
            return GameErrorCode.E_TONG_BING_NO_BATTLE;
        }

        TongBingProto.TongBingGetBattlefieldRespMsg.Builder builder = TongBingProto.TongBingGetBattlefieldRespMsg.newBuilder();
        builder.setRet(0);
        builder.setBattlefield(parseTongBingBattlefieldMsg(part, battlefield, city));

        // 进入场景
        UserBaseInfo userBaseInfo = CrossUserMgr.getUserBaseInfo(userId);
        if (userBaseInfo != null) {
            ScenePlayerInfo scenePlayerInfo = new ScenePlayerInfo(userBaseInfo.getServerId(), userId, userBaseInfo.getNickName(), userBaseInfo.getDecoration());
            UserScene userScene = new UserScene(
                    config.getActivityInfo().getActivityId(),
                    eSceneType.TongBingBattlefield.getValue(),
                    getBattlefieldSceneKey(cityId, part.getPartId()),
                    userId,
                    scenePlayerInfo,
                    System.currentTimeMillis());
            CrossSceneMgr.enterScene(userScene, false);
        }
        sendPacket(ClientProtocol.U_TONG_BING_GET_BATTLE_FIELD, builder, userId);
        return 0;
    }

    /**
     * 使用战鼓
     */
    public int useWarDrum(long userId, TongBingProto.TongBingUseWarDrumReqMsg reqMsg) {
        int cityId = reqMsg.getCityId();

        CrossTongBingCity city = getCrossTongBingData().getCityMap().get(cityId);
        if (city == null) {
            return GameErrorCode.E_TONG_BING_CITY_NO_EXIST;
        }

        CrossTongBingUserData userData = getUserData(userId);
        if (!isAble(userData)) {
            return GameErrorCode.E_TONG_BING_NO_ABLE;
        }

        synchronized (lock) {

            // 是否战斗中
            CrossTongBingBattlefield battlefield = city.getBattlefield();
            if (battlefield == null) {
                return GameErrorCode.E_TONG_BING_NO_BATTLE;
            }

            // 拿道具效果
            GoodsInfo goodsInfo = GoodsMgr.getGoodsInfo(config.getZhanGuItem());
            if (goodsInfo == null) {
                return GameErrorCode.E_BAG_ITEM_NO_EXIST;
            }
            int effectTimes = goodsInfo.getParamList().get(0).intValue();

            // 有基础城防军无法使用战鼓
            if (getDefTeamNum(city, battlefield, eTongBingSoldierType.COMMON_SOLDIER.getValue()) > 0) {
                return GameErrorCode.E_TONG_BING_HAS_DEF_TEAM;
            }
            if (getDefTeamNum(city, battlefield, eTongBingSoldierType.EXTRA_SOLDIER.getValue()) == 0) {
                for (CrossTongBingBattlefieldPart part : battlefield.getPartMap().values()) {
                    for (CrossTongBingBattlefieldTeam team : new LinkedList<>(part.getDefList())) {
                        if (team.getTeam() == null) {
                            return GameErrorCode.E_TONG_BING_HAS_DEF_TEAM;
                        }
                    }
                }
            }


            // 看看有没有自己的阵营在里面
            int myCampId = userData.getCampId();
            boolean hasMyCamp = false;
            if (city.getCampId() == myCampId) {
                hasMyCamp = true;
            }
            if (!hasMyCamp) {
                for (CrossTongBingTeamArrive arrive : new LinkedList<>(city.getAtkTeamList())) {
                    if (getUserData(arrive.getUserId()).getCampId() == myCampId) {
                        hasMyCamp = true;
                        break;
                    }
                }
            }
            if (!hasMyCamp) {
                for (CrossTongBingBattlefieldPart part : battlefield.getPartMap().values()) {
                    for (CrossTongBingBattlefieldTeam tempTeam : new LinkedList<>(part.getAtkList())) {
                        if (myCampId == getUserData(tempTeam.getTeam().getUserId()).getCampId()) {
                            hasMyCamp = true;
                            break;
                        }
                    }
                }
            }
            if (!hasMyCamp) {
                return GameErrorCode.E_TONG_BING_NO_MY_CAMP_TEAM;
            }

            // 【统兵演武取消加速限制】
//            // 当战场的加速场次＞较高方部队数/战场数的时候,无需加速
//            int atkTeamCnt = 0;
//            int defTeamCnt = 0;
//            for (CrossTongBingBattlefieldPart part : battlefield.getPartMap().values()) {
//                atkTeamCnt += part.getAtkList().size();
//                defTeamCnt += part.getDefList().size();
//            }
//            if (battlefield.getPartMap().size() != 0 && battlefield.getDrumTimes() > (Math.max(atkTeamCnt, defTeamCnt) / battlefield.getPartMap().size())) {
//                return GameErrorCode.E_TONG_BING_USE_DRUM_LIMIT;
//            }

            // 战鼓生效
            if (battlefield.getDrumTimes() <= 0) {
                battlefield.setUseDrumTime(System.currentTimeMillis());
            }
            battlefield.setDrumTimes(battlefield.getDrumTimes() + effectTimes);

            // 同步所有人
            for (CrossTongBingBattlefieldPart part : battlefield.getPartMap().values()) {
                syncUseWarDrum(city, battlefield, part);
            }
        }

        return 0;
    }

    /**
     * 获取城防军的数量
     */
    private int getSoldierTeamCount(CrossTongBingCity city) {
        return city.getExtraTeamNum() + city.getTeamNum();
    }

    // ============================== 移动 ==============================

    /**
     * 强制进入城池
     */
    private void forceArriveNextCity(CrossTongBingTeam team, long arriveTime) {
        synchronized (lock) {
            CrossTongBingUserData userData = getUserData(team.getUserId());
            ArrayList<Integer> newRouteList = new ArrayList<>(team.getRouteList());
            int cityId = newRouteList.remove(0);
            CrossTongBingCity city = getCrossTongBingData().getCityMap().get(cityId);

            // 是否为自己主城
            boolean selfMainCity = false;
            TongBingCityConfig cityConfig = config.getCityConfigMap().get(cityId);
            if (cityConfig.getType() == 1 && cityConfig.getInitialCamp() == userData.getCampId()) {
                selfMainCity = true;
            }

            // 【进城池队列】
            if (!selfMainCity) { // 自己主城不需要进进攻防守
                CrossTongBingTeamArrive arrive = new CrossTongBingTeamArrive(team.getUserId(), team.getOccupation(), arriveTime);
                if (city.getCampId() == userData.getCampId()) {
                    // 进入防守队列
                    city.getDefTeamList().addLast(arrive);
                } else {
                    // 进入进攻队列
                    city.getAtkTeamList().addLast(arrive);
                }
            }

            // 【开战】
            boolean haveCreateBattlefield = false;
            if (city.getBattlefield() == null && city.getAtkTeamList().size() > 0 && city.getDefTeamList().isEmpty() && getSoldierTeamCount(city) == 0) {
                // 对方没人，直接拿下
                handleNoBattleOccupy(city, arriveTime);
            } else if (city.getBattlefield() == null && city.getAtkTeamList().size() > 0) {
                // 开新战场
                createBattlefield(city);
                haveCreateBattlefield = true;
            } else if (city.getBattlefield() != null) {
                // 队伍加入战场
                teamToBattlefield(city, true);
            }

            boolean syncSelfToAll = (!newRouteList.isEmpty() && city.getBattlefield() != null);

            // 【修改队伍状态】
            team.setCityId(cityId);
            if (newRouteList.isEmpty() || city.getBattlefield() != null) {
                team.setBeginMoveTime(0);
                team.setRouteList(new ArrayList<>());
                team.setSmallMoveTime(0);
                team.setArriveTime(0);
                // 同步城池
                addNeedSyncCity(cityId);

                // 日志
                CrossAutoLogMgr.add(new LogTongBingTeamMove(
                        config.getActivityInfo().getActivityId(),
                        team.getUserId(),
                        team.getOccupation(),
                        team.getCityId(),
                        team.getRouteList(),
                        groupId
                ));
            } else {
                moveByRoute(team, newRouteList, true);
            }

            // 同步队伍
            if (haveCreateBattlefield) {
                syncCityTeamToSelf(city); // 城池开战，所有玩家改状态
            }
            if (syncSelfToAll) {
                addNeedSyncTeam(team); // 移动被打断，同步所有玩家
            }
            syncTeamToSelf(team.getUserId(), Collections.singletonList(team)); // 自己队伍要一直同步

            userData.setUpdateOption();
            crossTongBingData.setUpdateOption();
        }
    }

    /**
     * 计算下次到达时间
     */
    public long calcMoveTime(int campId, List<Integer> routeList) {
        // 最后一个城池，不是自己的，用慢速的时间，其余的用快速的
        if (routeList.size() == 1) {
            Integer lastCityId = routeList.get(0);
            CrossTongBingCity city = getCrossTongBingData().getCityMap().get(lastCityId);
            if (city != null && city.getCampId() != campId) {
                return config.getMoveTime();
            }
        }
        return config.getMoveTimeFast();
    }

    /**
     * 出发
     * （不会同步队伍）
     */
    public void moveByRoute(CrossTongBingTeam team, List<Integer> routeList, boolean continueMove) {
        // 队伍从旧城池移除
        int cityId = team.getCityId();
        CrossTongBingCity city = getCrossTongBingData().getCityMap().get(cityId);
        CrossTongBingTeamArrive arrive = null;
        for (CrossTongBingTeamArrive tmp : new LinkedList<>(city.getDefTeamList())) {
            if (tmp.getUserId() == team.getUserId() && tmp.getTeamId() == team.getOccupation()) {
                arrive = tmp;
                break;
            }
        }
        if (arrive != null) {
            city.getDefTeamList().remove(arrive);
        }

        // 出发
        if (!continueMove) {
            long now = System.currentTimeMillis();
            team.setBeginMoveTime(now);
            team.setRouteList(routeList);
            team.setSmallMoveTime(now);
            team.setArriveTime(now + calcMoveTime(getUserData(team.getUserId()).getCampId(), routeList));
        } else {
            team.setRouteList(routeList);
            team.setSmallMoveTime(team.getArriveTime());
            team.setArriveTime(team.getArriveTime() + calcMoveTime(getUserData(team.getUserId()).getCampId(), routeList));
        }
        getUserData(team.getUserId()).setUpdateOption();
        addTask(new CrossTongBingMoveTask(this, team.getArriveTime(), team.getUserId(), team.getOccupation()));
    }

    // ============================== 地图 ==============================

    private Map<Integer, CrossTongBingCity> createCity() {

        Map<Integer, CrossTongBingCity> cityMap = new ConcurrentHashMap<>();

        for (TongBingCityConfig cityConfig : config.getCityConfigMap().values()) {
            TongBingCityTypeConfig typeConfig = config.getCityTypeConfigMap().get(cityConfig.getType());

            CrossTongBingCity city = new CrossTongBingCity();
            city.setCityId(cityConfig.getId());
            city.setCampId(cityConfig.getInitialCamp());
            city.setTeamNum(typeConfig.getTeamNum());
            city.setTeamSoldierNum(typeConfig.getSoldierNum());
            Long startTime = config.getStage1TimeList().get(0).get(0);
            city.setCollectionTime(startTime);
            city.setLastRecoveryExtraTeamTime(startTime);
            cityMap.put(cityConfig.getId(), city);
        }

        return cityMap;
    }

    private Map<Integer, CrossTongBingCityEvent> createInitCityEvent() {
        List<Integer> cityIdList = config.getCityEventInitConfigList();
        List<Integer> configList = config.getCityEventConfigList();

        Map<Integer, CrossTongBingCityEvent> map = new ConcurrentHashMap<>();

        // 首次刷新时间 = 活动开始第一天的第一个刷新时间
        long zeroTime = DateHelper.getZeroTimeStamp(config.getStage1TimeList().get(0).get(0));
        long cityEventTime = getNextCityEventTime(zeroTime, true);

        for (int i = 0; i < cityIdList.size(); i++) {
            int campKey = i + 1;
            int buffId = configList.get(randomHelper.next(configList.size()));
            int cityId = cityIdList.get(i);

            CrossTongBingCityEvent event = new CrossTongBingCityEvent(
                    campKey,
                    cityId,
                    buffId,
                    cityEventTime
            );

            map.put(event.getCampKey(), event);
        }

        return map;
    }

    private int getBirthCityId(int campId) {
        for (TongBingCityConfig cityConfig : config.getCityConfigMap().values()) {
            if (cityConfig.getType() == 1 && cityConfig.getInitialCamp() == campId) {
                return cityConfig.getId();
            }
        }
        return 0;
    }

    /**
     * 前往城池
     */
    private int moveToCity(long userId, TongBingProto.TongBingMoveTeamReqMsg reqMsg) {
        CrossTongBingUserData userData = getUserData(userId);
        List<Integer> occupationList = reqMsg.getOccupationList();
        int toCityId = reqMsg.getToCityId();
        boolean isTargetCity = reqMsg.getIsTargetCity();

        // 时间判断
        eTongBingStage stage = this.config.getTongBingStage();
        if (stage != eTongBingStage.Stage1Time) {
            return GameErrorCode.E_TONG_BING_NO_OPERATE_TIME;
        }

        // 参数校验
        if (!isAble(userData)) {
            return GameErrorCode.E_TONG_BING_NO_ABLE;
        }
        for (Integer occu : occupationList) {
            if (!userData.getTeamMap().containsKey(occu)) {
                return GameErrorCode.E_TONG_BING_TEAM_NO_EXIST;
            }
        }

        synchronized (lock) {

            // 遍历所有队伍
            for (Integer occu : occupationList) {
                CrossTongBingTeam team = userData.getTeamMap().get(occu);

                // 跳过已经在这里的
                if (team.getCityId() == toCityId) {
                    continue;
                }
                // 前往城池
                int ret = moveToCity(team, toCityId);
                if (ret != 0) {
                    return ret;
                }
                if (isTargetCity) {
                    team.setTargetCityId(toCityId);
                }
            }
        }
        return 0;
    }

    /**
     * 前往城池
     */
    private int moveToCity(CrossTongBingTeam team, int toCityId) {
        TongBingCityConfig toCityConfig = config.getCityConfigMap().get(toCityId);
        if (toCityConfig == null) {
            return GameErrorCode.E_TONG_BING_CITY_NO_EXIST;
        }
        // 时间判断
        eTongBingStage stage = this.config.getTongBingStage();
        if (stage != eTongBingStage.Stage1Time) {
            return GameErrorCode.E_TONG_BING_NO_OPERATE_TIME;
        }

        // 状态判断
        if (getTeamStatus(team) != eTongBingTeamStatus.Free) {
            return GameErrorCode.E_TONG_BING_TEAM_CHANGE_SOLDIER_NO_FREE;
        }

        // 判断是否达到兵力最低要求,不满足的跳过处理
        // 千分比最低兵力和固定最低兵力取最大值
        long minSoldierNum = BigDecimal.valueOf(team.getMaxSoldier())
                .multiply(BigDecimal.valueOf(config.getTeamNumLowerLimitParam()))
                .divide(BigDecimal.valueOf(1000), 0, BigDecimal.ROUND_UP).longValue();
        if (team.getSoldierNum() < Math.max(minSoldierNum, config.getTeamNumLowerLimitFixedParam())) {
            return GameErrorCode.E_TONG_BING_TEAM_SOLDIER_MIN_LIMIT;
        }
        // 找路径
        List<Integer> cityRouteList = getRouteList(team, toCityId);

        if (cityRouteList == null || cityRouteList.size() <= 1) {
            return GameErrorCode.E_TONG_BING_NO_ROUTE;
        }
        // 出发点不要
        cityRouteList.remove(0);

        // 移动
        moveByRoute(team, cityRouteList, false);
        addNeedSyncTeam(team);

        // 日志
        CrossAutoLogMgr.add(new LogTongBingTeamMove(
                config.getActivityInfo().getActivityId(),
                team.getUserId(),
                team.getOccupation(),
                team.getCityId(),
                team.getRouteList(),
                groupId
        ));
        return 0;
    }

    /**
     * 获取当前队伍到指定城池的路径
     * @param team
     * @param toCityId
     */
    private List<Integer> getRouteList(CrossTongBingTeam team, int toCityId) {
        TongBingCityConfig toCityConfig = config.getCityConfigMap().get(toCityId);
        if (toCityConfig == null) {
            return null;
        }
        CrossTongBingUserData userData = getUserData(team.getUserId());
        TongBingCityConfig curCityConfig = config.getCityConfigMap().get(team.getCityId());
        // 能走的格子，未处于战斗状态
        Map<Integer, List<Integer>> canMoveCityMap = new ConcurrentHashMap<>();
        // 能走的格子，处于战斗状态
        Map<Integer, List<Integer>> battlefieldRouteMap = new ConcurrentHashMap<>();
        for (CrossTongBingCity city : crossTongBingData.getCityMap().values()) {
            // 没有在战斗的己方城池
            if (city.getCampId() == userData.getCampId()) {
                TongBingCityConfig safeCityConfig = config.getCityConfigMap().get(city.getCityId());
                if (city.getBattlefield() == null) {
                    canMoveCityMap.put(safeCityConfig.getId(), config.getCityConnectMap().get(safeCityConfig.getId()));
                } else {
                    battlefieldRouteMap.put(safeCityConfig.getId(), config.getCityConnectMap().get(safeCityConfig.getId()));
                }
            }
        }

        // 判断是否移除中心城
        if (System.currentTimeMillis() < config.getStage2BeginTime()) {
            for (TongBingCityConfig config : config.getCityConfigMap().values()) {
                if (config.getType() == 6) {
                    canMoveCityMap.remove(config.getId());
                    if (toCityId == config.getId()) {
                        return null;
                    }
                }
            }
        }

        // 起点强制加进去
        canMoveCityMap.put(curCityConfig.getId(), config.getCityConnectMap().get(curCityConfig.getId()));
        return TongBingMapLogic.getRouteList(canMoveCityMap, battlefieldRouteMap, curCityConfig.getId(), toCityConfig.getId());
    }

    private int speedUp(long userId, int occupation, int times) {
        // 参数校验
        CrossTongBingUserData userData = getUserData(userId);
        if (!isAble(userData)) {
            return GameErrorCode.E_TONG_BING_NO_ABLE;
        }
        CrossTongBingTeam team = userData.getTeamMap().get(occupation);
        if (team == null) {
            return GameErrorCode.E_TONG_BING_TEAM_NO_EXIST;
        }

        // 时间判断
        eTongBingStage stage = this.config.getTongBingStage();
        if (stage != eTongBingStage.Stage1Time) {
            return GameErrorCode.E_TONG_BING_NO_OPERATE_TIME;
        }

        // 取道具效果
        GoodsInfo goodsInfo = GoodsMgr.getGoodsInfo(config.getShenXingItem());
        if (goodsInfo == null) {
            return GameErrorCode.E_GOODS_NO_EXIST;
        }
        long reduceTime = goodsInfo.getParamList().get(0).longValue() * DateHelper.SECOND_MILLIONS;
        long totalReduceTime = times * reduceTime;
        long realReduceTime = 0L;
        long now = System.currentTimeMillis();

        synchronized (lock) {
            while (getTeamStatus(team) == eTongBingTeamStatus.Move) {
                if (realReduceTime + team.getArriveTime() - now < totalReduceTime) {
                    // 可以到达
                    realReduceTime = realReduceTime + team.getArriveTime() - now;
                    team.setArriveTime(now);
                    forceArriveNextCity(team, team.getArriveTime());
                } else {
                    // 无法到达
                    team.setArriveTime(team.getArriveTime() - (totalReduceTime - realReduceTime));
                    realReduceTime = totalReduceTime;
                    // 到达任务加回去
                    addTask(new CrossTongBingMoveTask(this, team.getArriveTime(), userId, occupation));
                    break;
                }
            }
        }

        // 真实消耗道具数量
        int realTimes = BigDecimal.valueOf(realReduceTime).divide(BigDecimal.valueOf(reduceTime), 0, BigDecimal.ROUND_UP).intValue();

        // 返回
        TongBingProto.TongBingMoveSpeedUpRespMsg.Builder resp = TongBingProto.TongBingMoveSpeedUpRespMsg.newBuilder();
        resp.setRet(0);
        resp.setTeam(parseTongBingTeamMsg(userData, team));

        TongBingProto.CrossTongBingMoveSpeedUpRespMsg.Builder builder = TongBingProto.CrossTongBingMoveSpeedUpRespMsg.newBuilder();
        builder.setResp(resp);
        builder.setReqTimes(times);
        builder.setRealTimes(realTimes);
        sendPacket(GameProtocol.S_TONG_BING_MOVE_SPEED_UP_FROM_CROSS, builder, userId);
        return 0;
    }

    private List<Integer> getMiddleCityId(int campId1, int campId2) {
        CrossTongBingData data = getCrossTongBingData();

        Set<Integer> cityList1 = new HashSet<>();
        Set<Integer> cityList2 = new HashSet<>();
        for (CrossTongBingCity city : data.getCityMap().values()) {
            if (city.getCampId() == campId1) {
                cityList1.add(city.getCityId());
            } else if (city.getCampId() == campId2) {
                cityList2.add(city.getCityId());
            }
        }

        Set<Integer> middleCityId = new HashSet<>(TongBingMapLogic.getMiddleCityId(config.getCityConnectMap(), cityList1, cityList2));

        return new ArrayList<>(middleCityId);
    }

    private int getCityTeam(long userId, TongBingProto.TongBingSyncCityTeamReqMsg reqMsg) {
        // 解析参数
        Map<Integer, Long> cityTimeMap = new ConcurrentHashMap<>();
        for (TongBingProto.TongBingSyncCityTeamMsg cityTeamMsg : reqMsg.getCityInfoList()) {
            cityTimeMap.put(cityTeamMsg.getCityId(), cityTeamMsg.getTime());
        }

        long now = System.currentTimeMillis();

        // 遍历全部玩家，把城池内移动中的队伍加进来
        Map<Integer, LinkedList<CrossTongBingShowTeam>> campTeamMap = new HashMap<>();
        for (CrossTongBingUserData userData : userDataMap.values()) {
            if (!isAble(userData)) {
                continue; // 跳过：无资格
            }

            campTeamMap.putIfAbsent(userData.getCampId(), new LinkedList<>());

            for (CrossTongBingTeam team : userData.getTeamMap().values()) {
                if (!cityTimeMap.containsKey(team.getCityId())) {
                    continue; // 跳过：不在该城市的队伍
                }

                if (getTeamStatus(team) == eTongBingTeamStatus.Move) {
                    campTeamMap.get(userData.getCampId()).add(new CrossTongBingShowTeam(userData, team));
                }
            }
        }

        // 排序
        CrossTongBingUserData myUserData = getUserData(userId);
        int myCampId = myUserData.getCampId();
        String myUnionUid = myUserData.getUnionUid();
        for (LinkedList<CrossTongBingShowTeam> teamLinkedList : campTeamMap.values()) {
            teamLinkedList.sort(new Comparator<CrossTongBingShowTeam>() {
                @Override
                public int compare(CrossTongBingShowTeam o1, CrossTongBingShowTeam o2) {
                    CrossTongBingUserData userData1 = o1.getUserData();
                    CrossTongBingUserData userData2 = o2.getUserData();
                    // 【同阵营比较】
                    if (userData1.getCampId() == myCampId) {
                        // 自己的队伍优先
                        if (userData1.getUserId() == userId || userData2.getUserId() == userId) {
                            if (userData1.getUserId() != userData2.getUserId()) {
                                return userData1.getUserId() == userId ? 1 : -1;
                            }
                        }
                        // 同商会优先
                        boolean unionEq1 = Objects.equals(userData1.getUnionUid(), myUnionUid);
                        boolean unionEq2 = Objects.equals(userData2.getUnionUid(), myUnionUid);
                        if (unionEq1 || unionEq2) {
                            if (unionEq1 != unionEq2) {
                                return unionEq1 ? 1 : -1;
                            }
                        }
                    }
                    // 【通用比较】
                    // 战功优先
                    if (userData1.getZhanGong() != userData2.getZhanGong()) {
                        return userData1.getZhanGong() > userData2.getZhanGong() ? 1 : -1;
                    }
                    return 0;
                }
            }.reversed());
        }

        // 找需要发给客户端的
        int campTeamNum = config.getShowCampTopNum();
        List<CrossTongBingTeam> teamList = new ArrayList<>();
        for (LinkedList<CrossTongBingShowTeam> linkedList : campTeamMap.values()) {
            int num = 0;
            for (CrossTongBingShowTeam showTeam : linkedList) {
                if (num++ >= campTeamNum) {
                    break;
                }
                CrossTongBingTeam team = showTeam.getTeam();
                Long lReqTime = cityTimeMap.get(team.getCityId());
                if(null == lReqTime){
                    continue;
                }
                long updateMoveTime = team.getMoveUpdateTime();
                if (updateMoveTime - lReqTime > DateHelper.SECOND_MILLIONS * 60) {
                    teamList.add(team);
                }
            }
        }

        // 返回
        TongBingProto.TongBingSyncCityTeamRespMsg.Builder builder = TongBingProto.TongBingSyncCityTeamRespMsg.newBuilder();
        for (TongBingProto.TongBingSyncCityTeamMsg cityMsg : reqMsg.getCityInfoList()) {
            builder.addCityInfo(cityMsg.toBuilder().setTime(now));
        }
        for (CrossTongBingTeam team : teamList) {
            builder.addTeam(parseTongBingTeamMsg(getUserData(team.getUserId()), team));
        }
        sendPacket(ClientProtocol.U_TONG_BING_SYNC_CITY_TEAM, builder, userId);
        return 0;
    }


    // ============================== 报名 ==============================

    private int unionJoin(long userId, TongBingProto.CrossTongBingJoinReqMsg reqMsg) {
        if (StringUtils.isNullOrEmpty(reqMsg.getUnionUid())) {
            return GameErrorCode.E_TONG_BING_NO_UNION;
        }
        if (config.getTongBingStage() != eTongBingStage.JoinTime) {
            return GameErrorCode.E_TONG_BING_NO_IN_JOIN_TIME;
        }
        synchronized (lock) {
            if (unionJoinMap.containsKey(reqMsg.getUnionUid())) {
                return GameErrorCode.E_TONG_BING_HAD_JOIN;
            }

            // 报名
            CrossTongBingUnionJoin join = new CrossTongBingUnionJoin();
            join.setActivityId(reqMsg.getActivityId());
            join.setGroupId(groupId);
            join.setUnionUid(reqMsg.getUnionUid());
            join.setUnionBaseInfo(UnionBasePb.parseUnionBaseInfo(reqMsg.getUnionInfo()));
            join.setMemberMap(new ConcurrentHashMap<>());
            join.setInsertOption();
            unionJoinMap.put(join.getUnionUid(), join);

            // 拉一下数据
            sendGetUnionMsg(1, Collections.singletonList(reqMsg.getUnionUid()), 0);
        }

        // 日志
        CrossAutoLogMgr.add(new LogTongBingJoin(
                config.getActivityInfo().getActivityId(),
                userId,
                reqMsg.getUnionUid(),
                0,
                groupId
        ));

        return 0;
    }

    /**
     * 请求商会跨服拿成员数据
     *
     * @param type     1根据unionUid，2根据数量
     * @param unionUid 不需要可以空list
     * @param count    不需要就传0
     */
    public void sendGetUnionMsg(int type, List<String> unionUid, int count) {
        TongBingProto.CrossTongBingGetUnionReqMsg.Builder builder = TongBingProto.CrossTongBingGetUnionReqMsg.newBuilder();
        builder.setActivityId(config.getActivityInfo().getActivityId());
        List<Long> serverIdList = config.getActivityInfo().getServerIdList();
        builder.setGameServerId(serverIdList.get(new Random().nextInt(serverIdList.size())));
        builder.setType(type);
        if (type == 1) {
            for (String s : unionUid) {
                builder.addUnionUid(s);
            }
        } else if (type == 2) {
            builder.setEarnSpeedTopCount(count);
            builder.addAllServerId(config.getActivityInfo().getServerIdList());
        }
        builder.setGroupId(groupId);
        MessageHelper.sendPacket(builder.getGameServerId(), 0, YanQuMessageUtils.buildMessage(CrossProtocol.C3_CROSS_TONG_BING_GET_UNION_REQ, builder));
    }

    /**
     * 请求游戏服上传玩家信息
     */
    private void sendGetUnionMemberMsg(long serverId, List<Long> userId) {
        TongBingProto.CrossTongBingGetMemberReqMsg.Builder builder = TongBingProto.CrossTongBingGetMemberReqMsg.newBuilder();
        builder.setActivityId(config.getActivityInfo().getActivityId());
        builder.addAllUserId(userId);
        builder.setGroupId(groupId);
        MessageHelper.sendPacket(serverId, 0, YanQuMessageUtils.buildMessage(GameProtocol.S_CROSS_TONG_BING_GET_UNION_MEMBER_REQ, builder));
    }

    /**
     * 锁定商会
     */
    private void lockUnion() {
        synchronized (lock) {
            if (getCrossTongBingData().isLockUnion()) {
                return;
            }

            // 缓存一下门客赚钱
            for (CrossTongBingUnionJoin join : unionJoinMap.values()) {
                join.reCalcAbility();
            }

            // 排序拿前几名
            List<CrossTongBingUnionJoin> rankList = unionJoinMap.values().stream()
                    .sorted(new Comparator<CrossTongBingUnionJoin>() {
                        @Override
                        public int compare(CrossTongBingUnionJoin o1, CrossTongBingUnionJoin o2) {
                            // 922 3372 0368亿
                            return Long.compare(o2.getTotalAbility(), o1.getTotalAbility());
                        }
                    })
                    .limit(config.getUnionNum())
                    .collect(Collectors.toList());

            for (int i = 0; i < rankList.size(); i++) { // 总赚钱排名
                CrossTongBingUnionJoin union = rankList.get(i);
                union.setOldRank(i + 1);
            }

            // 分阵营
            List<Long> abilityParams = config.getGuildGroupRuleParams();
            List<Integer> groupParams = config.getGroupParams();
            if (abilityParams.isEmpty() || groupParams.isEmpty()) { // 如果未配置战力压缩规则或特殊分组范围，就走旧的分配阵营逻辑
                lockCamp(rankList);
            } else {
                List<Integer> secondMatchRanges = config.secondMatchRanges();
                if (abilityParams.isEmpty() || secondMatchRanges.isEmpty()) {
                    logger.error("统兵演武 activity {} union lock error. 商会分组范围参数或者战力压缩参数没配置", config.getActivityInfo().getActivityId());
                    return;
                }
                lockCamp(rankList, secondMatchRanges, abilityParams);
            }

            //打补丁
            for (CrossTongBingUnionJoin union : unionJoinMap.values()) {
                if (union.getCampId() == 0) {
                    logger.error("统兵campId为0,activityId={},groupId={},unionUid={}", config.getActivityInfo().getActivityId(), groupId, union.getUnionUid());
                    continue;
                }

                for (CrossTongBingUnionJoinMember unionMember : union.getMemberMap().values()) {
                    CrossTongBingUserData userData = getUserData(unionMember.getUserId());
                    userData.setCampId(union.getCampId());
                    userData.setUnionUid(union.getUnionUid());
                }
            }

            // 改状态
            getCrossTongBingData().setLockUnion(true);
            logger.error("tongbing activity {} lock union.", config.getActivityInfo().getActivityId());

            // 移动玩家到起始点
            crossTongBingInitTeamPosTask();

            // 初始化榜单，让0值的也上榜，才能看到自己阵营都有谁
            initAllRankList();

            // 同步状态
            for (CrossTongBingUserData userData : userDataMap.values()) {
                syncBaseDataMsg(userData);
                // 跨服同步用户数据到游戏服
                syncUserDataToGameServer(userData);
            }
        }

        // 日志
        for (CrossTongBingUnionJoin join : unionJoinMap.values()) {
            CrossAutoLogMgr.add(new LogTongBingLockUnion(
                    config.getActivityInfo().getActivityId(),
                    join.getUnionUid(),
                    join.getCampId(),
                    join.getUnionBaseInfo().getTotalEarnSpeed().toString(),
                    join.getCampId() == 0 ? "-1" : join.getTotalAbility() + "",
                    join.getCampId() != 0 ? 1 : 0,
                    groupId
            ));
        }
    }

    /**
     * 锁定阵营
     *
     * @param unionJoinList 参与活动的商会列表
     * @param abilityParams 压缩战力参数
     * @param groupParams   特殊分组参数
     */
    public void newLockCamp(List<CrossTongBingUnionJoin> unionJoinList, List<Long> abilityParams,
                            List<Integer> groupParams) {
        synchronized (lock) {
            if (getCrossTongBingData().isLockUnion()) {
                return;
            }

            // 容错：先清空锁定数据
            for (CrossTongBingUserData userData : userDataMap.values()) {
                if (isAble(userData)) {
                    userData.setCampId(0);
                    userData.setUnionUid("");
                }
            }
            for (CrossTongBingUnionJoin join : unionJoinMap.values()) {
                if (isAble(join)) {
                    join.setCampId(0);
                }
            }

            unionJoinList.sort((o1, o2) -> Long.compare(o2.getTotalAbility(), o1.getTotalAbility()));
            int activityId = config.getActivityInfo().getActivityId();
            for (int i = 0; i < unionJoinList.size(); i++) {
                CrossTongBingUnionJoin unionJoin = unionJoinList.get(i);
                logger.info("活动ID{}--原先按商会总赚钱排名{}--商会UID{}--商会总赚钱{}", activityId, i + 1, unionJoin.getUnionUid(), unionJoin.getTotalAbility());
            }

            // 对所有商会进行战力压缩
            long p0 = abilityParams.get(0);
            long p1 = abilityParams.get(1);
            long p2 = abilityParams.get(2);
            long p3 = abilityParams.get(3);
            if (p0 <= 5 && p1 <= 2 && p2 <= 5 && p3 <= 5) {
                for (CrossTongBingUnionJoin union : unionJoinList) {
                    BigDecimal totalAbility = new BigDecimal(union.getTotalAbility() < 0 ? 0 :
                            union.getTotalAbility()); // 商会总赚钱，别让根号下的数字为负数
                    BigDecimal memberNum = new BigDecimal(union.getMemberMap().size()); // 商会总人数
                    // 利用公式对总赚钱进行战力压缩
                    // x = p0√totalAbility * memberNum
                    // y = (x^p1) / p2 - p3 * x
                    try {
                        BigDecimal x = BigDecimal.valueOf(Math.pow(totalAbility.doubleValue(),
                                1.0 / abilityParams.get(0))).multiply(memberNum);
                        BigDecimal y = BigDecimal.valueOf(Math.pow(x.doubleValue(), abilityParams.get(1)))
                                .divide(BigDecimal.valueOf(abilityParams.get(2)), 8, RoundingMode.HALF_UP)
                                .subtract(BigDecimal.valueOf(abilityParams.get(3)).multiply(x));
                        union.setCompressedAbility(y);
                    } catch (Exception ex) {
                        logger.error("UUID{} 总战力{} 总人数{} 战力压缩错误!", union.getUnionUid(), totalAbility, memberNum);
                        logger.error("p0{} p1{} p2{} p3{}, 分组参数", p0, p1, p2, p3);
                        union.setCompressedAbility(BigDecimal.valueOf(Integer.MIN_VALUE));
                    }
                }
            } else if (p0 > 5 && p1 > 2 && p2 > 5 && p3 >= 5) {
                for (CrossTongBingUnionJoin union : unionJoinList) {
                    long totalAbility = union.getTotalAbility(); // 商会总赚钱
                    int memberNum = union.getMemberMap().size(); // 商会总人数
                    try {
                        // 利用公式对总赚钱进行战力压缩  v = log(p2)(totalAbility / p0 * p1) * p3 * memberNum;
                        BigDecimal tmp = BigDecimal.valueOf(totalAbility).divide(BigDecimal.valueOf(abilityParams.get(0))
                                , 9, RoundingMode.HALF_UP).multiply(BigDecimal.valueOf(abilityParams.get(1)));
                        BigDecimal tmp1 =
                                BigDecimal.valueOf(Math.log(tmp.doubleValue())).divide(BigDecimal.valueOf(Math.log(abilityParams.get(2))), 9,
                                        RoundingMode.HALF_UP);
                        union.setCompressedAbility(tmp1.multiply(BigDecimal.valueOf(abilityParams.get(3))).multiply(BigDecimal
                                .valueOf(memberNum)));
                    } catch (Exception ex) {
                        logger.error("UUID{} 总战力{} 总人数{} 战力压缩错误!", union.getUnionUid(), totalAbility, memberNum);
                        logger.error("p0{} p1{} p2{} p3{}, 分组参数!", p0, p1, p2, p3);
                        union.setCompressedAbility(BigDecimal.valueOf(Integer.MIN_VALUE));
                    }
                }
            }

            // 按压缩战力重新排序
            unionJoinList.sort((o1, o2) -> {
                if (o1.getCompressedAbility().compareTo(o2.getCompressedAbility()) == 0) { // 压缩战力一致，比较总赚钱
                    return Long.compare(o2.getTotalAbility(), o1.getTotalAbility());
                }
                return o2.getCompressedAbility().compareTo(o1.getCompressedAbility());
            });

            for (int i = 0; i < unionJoinList.size(); i++) {
                CrossTongBingUnionJoin unionJoin = unionJoinList.get(i);
                logger.info("活动ID{}--按压缩战力排名{}--商会UID{}--商会压缩战力{}--商会总赚钱{}", activityId, i + 1, unionJoin.getUnionUid(),
                        unionJoin.getCompressedAbility(),
                        unionJoin.getTotalAbility());
            }

            int unionCnt = unionJoinList.size(); // 商会总数
            int campNum = config.getCampNum(); // 阵营总数
            // 将压缩后总赚钱前三名的商会分别分配至不同阵营（魏蜀吴）
            // 在保证有 campNum 个以上的商会参加则为1-campNum，否则取最小值
            // 优先弹出最小压缩战力
            PriorityBlockingQueue<Camp> camps =
                    new PriorityBlockingQueue<>(campNum, Comparator.comparing(Camp::getCampAbility));
            for (int idx = 0; idx < Math.min(campNum, unionCnt); idx++) {
                CrossTongBingUnionJoin union = unionJoinList.get(idx);
                List<String> tmp = new ArrayList<>();
                tmp.add(union.getUnionUid());
                Camp camp = new Camp(idx + 1, tmp);
                camp.setCampAbility(union.getCompressedAbility());
                camps.add(camp);
                // 修改商会信息
                union.setCampId(idx + 1);
                for (CrossTongBingUnionJoinMember member : union.getMemberMap().values()) {
                    CrossTongBingUserData userData = getUserData(member.getUserId());
                    userData.setCampId(union.getCampId());
                    userData.setUnionUid(union.getUnionUid());
                }
            }

            // 随机排序的范围，在保证有9个以上的商会参加则为4-9，否则取最小值
            int right = Math.min(groupParams.get(1), unionCnt);
            List<CrossTongBingUnionJoin> shuffled = new ArrayList<>();
            for (int idx = groupParams.get(0) - 1; idx < right; idx++) {
                shuffled.add(unionJoinList.get(idx));
            }
            Collections.shuffle(shuffled); // 乱序

            for (int i = 0; i < shuffled.size(); i++) {
                CrossTongBingUnionJoin unionJoin = shuffled.get(i);
                logger.info("活动ID{}--乱序后{}--商会UID{}--商会压缩战力{}--商会总赚钱{}", activityId, i + 1, unionJoin.getUnionUid(),
                        unionJoin.getCompressedAbility(),
                        unionJoin.getTotalAbility());
            }

            // 分组，并按组总压缩战力排序
            List<List<CrossTongBingUnionJoin>> groups = new ArrayList<>();
            int idx1 = 0;
            while (idx1 < shuffled.size()) {
                List<CrossTongBingUnionJoin> sub = new ArrayList<>();
                while (idx1 < shuffled.size() && sub.size() < campNum) {
                    sub.add(shuffled.get(idx1++));
                }
                groups.add(sub);
            }
            groups.sort(Comparator.comparingDouble(o -> o.stream().mapToDouble(CrossTongBingUnionJoin::getCompressedAbilityDouble).sum()));

            for (int i = 0; i < groups.size(); i++) {
                List<CrossTongBingUnionJoin> sub = groups.get(i);
                logger.info("活动ID{}--乱序后{}--分组总战力{}", activityId, i + 1,
                        sub.stream().mapToDouble(CrossTongBingUnionJoin::getCompressedAbilityDouble).sum());
            }

            // 优先弹出最大压缩战力
            PriorityBlockingQueue<CrossTongBingUnionJoin> group =
                    new PriorityBlockingQueue<>(campNum, (o1, o2) -> {
                        if (o2.getCompressedAbility().compareTo(o1.getCompressedAbility()) == 0) { // 压缩战力一致，比较总赚钱
                            return Long.compare(o2.getTotalAbility(), o1.getTotalAbility());
                        }
                        return o2.getCompressedAbility().compareTo(o1.getCompressedAbility());
                    });

            int idx2 = 1;
            for (List<CrossTongBingUnionJoin> sub : groups) {
                group.addAll(sub);
                // 填充到阵营中
                Set<Camp> set = new HashSet<>(); // 存放弹出的阵营
                logger.info("活动ID{}--{} 分组填充", activityId, idx2++);
                while (!camps.isEmpty() && !group.isEmpty()) {
                    Camp camp = camps.poll();
                    set.add(camp);
                    CrossTongBingUnionJoin union = group.poll();
                    if (union == null) {
                        continue;
                    }
                    logger.info("活动ID{}--阵营{}--总压缩战力{}", activityId, camp.getCampId(),
                            camp.getCampAbility());
                    logger.info("活动ID{}--商会UID{}--商会压缩战力{}--商会总赚钱{}--加入阵营{}", activityId, union.getUnionUid(),
                            union.getCompressedAbility(),
                            union.getTotalAbility(), camp.getCampId());
                    camp.setCampAbility(camp.getCampAbility().add(union.getCompressedAbility()));
                    camp.getUnions().add(union.getUnionUid());
                    // 修改商会信息
                    union.setCampId(camp.getCampId());
                    for (CrossTongBingUnionJoinMember member : union.getMemberMap().values()) {
                        CrossTongBingUserData userData = getUserData(member.getUserId());
                        userData.setCampId(union.getCampId());
                        userData.setUnionUid(union.getUnionUid());
                    }
                }
                camps.addAll(set);
            }

            // 用于日志记录
            PriorityBlockingQueue<Camp> tmpCamps =
                    new PriorityBlockingQueue<>(campNum, Comparator.comparing(Camp::getCampAbility));

            // 剩余商会分配
            int campUnionNum = BigDecimal.valueOf(Math.min(config.getUnionNum(), unionCnt)) // 配置数量和实际数量取最小
                    .divide(BigDecimal.valueOf(config.getCampNum()), 0, BigDecimal.ROUND_UP).intValue();
            for (int idx = right; idx < unionCnt; idx++) {
                CrossTongBingUnionJoin union = unionJoinList.get(idx);
                if (!camps.isEmpty()) {
                    Camp camp = camps.poll();
                    camp.setCampAbility(camp.getCampAbility().add(union.getCompressedAbility()));
                    camp.getUnions().add(union.getUnionUid());
                    if (camp.getUnions().size() < campUnionNum) { // 超出阵营可容纳商会数则直接将阵营弹出
                        camps.add(camp);
                    }
                    if (camp.getUnions().size() >= campUnionNum) {
                        tmpCamps.add(camp);
                    }
                    // 修改商会信息
                    union.setCampId(camp.getCampId());
                    for (CrossTongBingUnionJoinMember member : union.getMemberMap().values()) {
                        CrossTongBingUserData userData = getUserData(member.getUserId());
                        userData.setCampId(union.getCampId());
                        userData.setUnionUid(union.getUnionUid());
                    }
                }
            }
            logger.info("活动ID{}--阵营商会未满", activityId);
            while (!camps.isEmpty()) {
                Camp camp = camps.poll();
                logger.info("活动ID{}--阵营{}--压缩战力{}", activityId, camp.getCampId(), camp.getCampAbility());
                for (int i = 0; i < camp.getUnions().size(); i++) {
                    String unionId = camp.getUnions().get(i);
                    for (CrossTongBingUnionJoin union : unionJoinList) {
                        if (union.getUnionUid().equals(unionId)) {
                            logger.info("活动ID{}--序号{}--第{}阵营--商会UID{}--商会压缩战力{}", activityId, i + 1, camp.getCampId(),
                                    union.getUnionUid(), union.getCompressedAbility());
                        }
                    }
                }
            }

            logger.info("活动ID{}--阵营商会已满", activityId);
            while (!tmpCamps.isEmpty()) {
                Camp camp = tmpCamps.poll();
                logger.info("活动ID{}--阵营{}--压缩战力{}", activityId, camp.getCampId(), camp.getCampAbility());
                for (int i = 0; i < camp.getUnions().size(); i++) {
                    String unionId = camp.getUnions().get(i);
                    for (CrossTongBingUnionJoin union : unionJoinList) {
                        if (union.getUnionUid().equals(unionId)) {
                            logger.info("活动ID{}--序号{}--第{}阵营--商会UID{}--商会压缩战力{}", activityId, i + 1, camp.getCampId(),
                                    union.getUnionUid(), union.getCompressedAbility());
                        }
                    }
                }
            }
            // 阵营数据生成
            initCampUserData();
        }
    }

    public void lockCamp(List<CrossTongBingUnionJoin> unionJoinList, List<Integer> secondMatchRanges, List<Long> abilityParams) {
        synchronized (lock) {
            if (getCrossTongBingData().isLockUnion()) {
                return;
            }

            // 容错：先清空锁定数据
            for (CrossTongBingUserData userData : userDataMap.values()) {
                if (isAble(userData)) {
                    userData.setCampId(0);
                    userData.setUnionUid("");
                }
            }
            for (CrossTongBingUnionJoin join : unionJoinMap.values()) {
                if (isAble(join)) {
                    join.setCampId(0);
                }
            }

            // 对第1名、第2名、第3名商会进行分配时，直接按照总赚钱进行阵营随机，并且三个商会必然会分在三个阵营中
            int campNum = config.getCampNum();
            int campUnionNum = BigDecimal.valueOf(Math.min(config.getUnionNum(), unionJoinList.size())) // 配置数量和实际数量取最小
                    .divide(BigDecimal.valueOf(config.getCampNum()), 0, BigDecimal.ROUND_UP).intValue();

            List<CampUnion> campUnions = unionJoinList.stream()
                    .map(superClass -> (CampUnion) superClass)
                    .collect(Collectors.toList());

            com.yanqu.road.server.manager.activity.camp.Camp.divide(campUnions, secondMatchRanges, abilityParams, campNum, campUnionNum,
                    config.getActivityInfo().getActivityId(), groupId, eCampActivityType.TONGBING);
            // 阵营数据生成
            initCampUserData();
        }
    }

    @Override
    public BigDecimal calcMemberCompressedAbility(long ability, int memberNum, List<Long> abilityParams) {
        long p0 = abilityParams.get(0);
        long p1 = abilityParams.get(1);
        long p2 = abilityParams.get(2);
        long p3 = abilityParams.get(3);
        if (p0 <= 5 && p1 <= 2 && p2 <= 5 && p3 <= 5) {
            // 利用公式对总赚钱进行战力压缩
            // x = p0√totalAbility * memberNum   y = (x^p1) / p2 - p3 * x
            BigDecimal x = BigDecimal.valueOf(Math.pow(ability,
                    1.0 / p0)).multiply(BigDecimal.valueOf((memberNum)));
            return BigDecimal.valueOf(Math.pow(x.doubleValue(), p1)).divide(BigDecimal.valueOf(p2), 8, RoundingMode.HALF_UP)
                    .subtract(BigDecimal.valueOf(p3).multiply(x));
        } else if (p0 > 5 && p1 > 2 && p2 > 5 && p3 >= 5) {
            // 利用公式对总赚钱进行战力压缩  v = log(p2)(totalAbility / p0 * p1) * p3 * memberNum;
            BigDecimal tmp = BigDecimal.valueOf(ability).divide(BigDecimal.valueOf(p0), 9, RoundingMode.HALF_UP).multiply(BigDecimal.valueOf(p1));
            BigDecimal tmp1 = BigDecimal.valueOf(Math.log(tmp.doubleValue())).divide(BigDecimal.valueOf(Math.log(p2)), 9, RoundingMode.HALF_UP);
            return tmp1.multiply(BigDecimal.valueOf(p3)).multiply(BigDecimal.valueOf(memberNum));
        }

        return null;
    }

    /**
     * 原先的阵营分配逻辑
     *
     * @param unionJoinList 参与活动的商会列表
     */
    private void lockCamp(List<CrossTongBingUnionJoin> unionJoinList) {
        synchronized (lock) {
            if (getCrossTongBingData().isLockUnion()) {
                return;
            }

            // 容错：先清空锁定数据
            for (CrossTongBingUserData userData : userDataMap.values()) {
                if (isAble(userData)) {
                    userData.setCampId(0);
                    userData.setUnionUid("");
                }
            }
            for (CrossTongBingUnionJoin join : unionJoinMap.values()) {
                if (isAble(join)) {
                    join.setCampId(0);
                }
            }

            // 按门客赚钱重新排序
            unionJoinList = unionJoinList.stream().sorted(new Comparator<CrossTongBingUnionJoin>() {
                @Override
                public int compare(CrossTongBingUnionJoin o1, CrossTongBingUnionJoin o2) {
                    return Long.compare(o2.getTotalAbility(), o1.getTotalAbility());
                }
            }).collect(Collectors.toList());

            Map<Integer, Long> campAbilityMap = new ConcurrentHashMap<>();
            Map<Integer, List<String>> campUnionMap = new ConcurrentHashMap<>();

            int campNum = config.getCampNum();
            int campUnionNum = BigDecimal.valueOf(Math.min(config.getUnionNum(), unionJoinList.size())) // 配置数量和实际数量取最小
                    .divide(BigDecimal.valueOf(config.getCampNum()), 0, BigDecimal.ROUND_UP).intValue();
            for (int i = 1; i <= campNum; i++) {
                campAbilityMap.put(i, 0L);
                campUnionMap.put(i, new ArrayList<>());
            }

            // 遍历商会一个个分配
            for (CrossTongBingUnionJoin join : unionJoinList) {
                int minCamp = 1;
                long minAbility = Long.MAX_VALUE;
                for (Map.Entry<Integer, Long> entry : campAbilityMap.entrySet()) {
                    // 跳过已经分配满的阵营
                    if (campUnionMap.get(entry.getKey()).size() >= campUnionNum) {
                        continue;
                    }
                    // 取总赚钱最小的阵营（赚钱相同时，随机一下，避免一直一样）
                    if (entry.getValue() < minAbility ||
                            entry.getValue() == minAbility && randomHelper.next(2) == 0) {
                        minCamp = entry.getKey();
                        minAbility = entry.getValue();
                    }
                }

                long sum = join.getMemberMap().values().stream().mapToLong(CrossTongBingUnionJoinMember::getTopAbility).sum();
                campAbilityMap.put(minCamp, campAbilityMap.get(minCamp) + sum);
                campUnionMap.get(minCamp).add(join.getUnionUid());

                // 修改数据
                join.setCampId(minCamp);
                for (CrossTongBingUnionJoinMember member : join.getMemberMap().values()) {
                    CrossTongBingUserData userData = getUserData(member.getUserId());
                    userData.setCampId(join.getCampId());
                    userData.setUnionUid(join.getUnionUid());
                }
            }

            // 阵营数据生成
            initCampUserData();
        }
    }

    /**
     * 是否有资格
     */
    private boolean isAble(CrossTongBingUserData userData) {
        return userData.getCampId() != 0;
    }

    /**
     * 是否有资格
     */
    private boolean isAble(CrossTongBingUnionJoin join) {
        return join.getCampId() != 0;
    }

    // ============================== 红点 ==============================

    /**
     * 添加红点
     */
    private void addRedDot(CrossTongBingUserData userData, eTongBingRedDotType type) {
        if (userData.getRedDotMap().containsKey(type.getValue())) {
            return;
        }
        userData.getRedDotMap().put(type.getValue(), System.currentTimeMillis());
        userData.setUpdateOption();

        TongBingProto.TongBingSyncRedDotRespMsg.Builder builder = TongBingProto.TongBingSyncRedDotRespMsg.newBuilder();
        builder.addType(type.getValue());
        sendPacket(ClientProtocol.U_TONG_BING_SYNC_RED_DOT, builder, userData.getUserId());
    }

    // ============================== buff ==============================

    /**
     * 获取buff效果
     *
     * @return 看配置下标是哪个就拿哪个
     */
    private List<Integer> getBuffAddition(long userId, eTongBingBuffType buffType) {
        return getBuffAddition(getUserData(userId).getCampId(), buffType);
    }


    /**
     * 获取buff效果
     *
     * @return 看配置下标是哪个就拿哪个
     */
    private List<Integer> getBuffAddition(int campId, eTongBingBuffType buffType) {
        // 目前最多一个buff有2个效果
        List<Integer> resultList = new ArrayList<>();
        resultList.add(0);
        resultList.add(0);

        // 判断阵营
        if (campId == 0) {
            return resultList;
        }

        // 这几种buff从城池事件那边找
        if (buffType == eTongBingBuffType.Type1_TeamSoldierMaxNum
                || buffType == eTongBingBuffType.Type2_RecruitSoldierPrice
                || buffType == eTongBingBuffType.Type3_CityTeamMaxNum
                || buffType == eTongBingBuffType.Type4_Hospital) {
            List<CrossTongBingCampBuff> list = getCrossTongBingData().getCampBuffMap().get(campId);
            if (list != null) {
                for (CrossTongBingCampBuff campBuff : new ArrayList<>(list)) {
                    TongBingBuffTypeConfig buffTypeConfig = config.getBuffTypeConfigMap().get(campBuff.getBuffId());
                    if (buffTypeConfig != null) {
                        if (buffTypeConfig.getType() == buffType.getValue()) {
                            // buff加到结果中
                            List<Integer> effectList = StringUtils.stringToIntegerList(buffTypeConfig.getBuffEffect(), ";");
                            for (int i = 0; i < effectList.size(); i++) {

                                if (buffType == eTongBingBuffType.Type2_RecruitSoldierPrice && i == 0) {
                                    // 不正常乘算
                                    resultList.set(i, calcBuffType2_0(resultList.get(i), effectList.get(i)));
                                } else {
                                    // 正常加算
                                    resultList.set(i, resultList.get(i) + effectList.get(i));
                                }

                            }
                        }
                    }
                }
            }
            return resultList;
        }

        // 这几种buff从占领的城池找
        if (buffType == eTongBingBuffType.Type5_CityFoodProduce ||
                buffType == eTongBingBuffType.Type6_ReadySoldierMaxNum) {
            for (CrossTongBingCity city : getCrossTongBingData().getCityMap().values()) {
                if (city.getCampId() == campId) {
                    TongBingCityConfig cityConfig = config.getCityConfigMap().get(city.getCityId());
                    TongBingCityTypeConfig cityTypeConfig = config.getCityTypeConfigMap().get(cityConfig.getType());
                    if (cityTypeConfig.getBuffEffect() != 0) {
                        TongBingBuffTypeConfig buffTypeConfig = config.getBuffTypeConfigMap().get(cityTypeConfig.getBuffEffect());
                        if (buffTypeConfig.getType() == buffType.getValue()) {
                            // buff加到结果中
                            List<Integer> effectList = StringUtils.stringToIntegerList(buffTypeConfig.getBuffEffect(), ";");
                            for (int i = 0; i < effectList.size(); i++) {
                                resultList.set(i, resultList.get(i) + effectList.get(i));
                            }
                        }
                    }
                }
            }
        }

        return resultList;
    }

    /**
     * 计算招兵减银两buff叠加效果
     * （减数值、多个buff叠加乘算）
     *
     * @param value1 之前BUFF全部叠加的减少值，千分比
     * @param value2 当前BUFF减少值，千分比
     */
    private int calcBuffType2_0(int value1, int value2) {

        // 先算实际消耗倍是原来多少倍，再用1减一下，就是buff减少的倍数
        // 1 - (1-效果1)(1-效果2)  =  效果1 + 效果2 - 效果1*效果2

        BigDecimal a1 = BigDecimal.valueOf(value1).divide(BigDecimal.valueOf(1000), 8, BigDecimal.ROUND_UP);
        BigDecimal a2 = BigDecimal.valueOf(value2).divide(BigDecimal.valueOf(1000), 8, BigDecimal.ROUND_UP);
        BigDecimal result = a1.add(a2).subtract(a1.multiply(a2));

        // 转千分比再返回
        return result.multiply(BigDecimal.valueOf(1000)).setScale(0, BigDecimal.ROUND_DOWN).intValue();
    }

    private List<Integer> getBuffAdditionParam(CrossTongBingCampBuff buff) {
        TongBingBuffTypeConfig buffTypeConfig = config.getBuffTypeConfigMap().get(buff.getBuffId());
        return StringUtils.stringToIntegerList(buffTypeConfig.getBuffEffect(), ";");
    }

    private long getNextCityEventTime(long lastCityEventTime, boolean isRefresh) {
        List<Long> configList = isRefresh ? config.getCityEventRefreshTime() : config.getCityEventApplyTime();

        long zeroTimeStamp = DateHelper.getZeroTimeStamp(lastCityEventTime);
        if (lastCityEventTime >= zeroTimeStamp + configList.get(configList.size() - 1)) {
            zeroTimeStamp += DateHelper.DAY_MILLIONS; // 那天全部刷新完，就刷新下一天的
        }

        long resultTime = Long.MAX_VALUE;
        for (Long configTime : configList) {
            long tmpTime = zeroTimeStamp + configTime;
            if (tmpTime > lastCityEventTime) {
                resultTime = tmpTime;
                break;
            }
        }

        return resultTime;
    }

    private long getBuffEndTime(CrossTongBingCampBuff buff) {
        TongBingBuffTypeConfig buffTypeConfig = config.getBuffTypeConfigMap().get(buff.getBuffId());
        if (buffTypeConfig.getContinueTime() == 0) {
            if (buff.getEndTime() != 0) {
                return buff.getEndTime(); // 城池易主结束时间
            } else {
                return config.getActivityInfo().getEndTime() * 1000; // 正常不会获取到这个
            }
        }
        return buff.getBeginTime() + buffTypeConfig.getContinueTime();
    }

    private void beforeEndBuff(CrossTongBingCampBuff buff) {
        int campId = buff.getCampId();
        long buffEndTime = getBuffEndTime(buff);
        TongBingBuffTypeConfig buffTypeConfig = config.getBuffTypeConfigMap().get(buff.getBuffId());
        if (buffTypeConfig.getType() == eTongBingBuffType.Type1_TeamSoldierMaxNum.getValue()) {

        } else if (buffTypeConfig.getType() == eTongBingBuffType.Type2_RecruitSoldierPrice.getValue()) {

        } else if (buffTypeConfig.getType() == eTongBingBuffType.Type3_CityTeamMaxNum.getValue()) {

        } else if (buffTypeConfig.getType() == eTongBingBuffType.Type4_Hospital.getValue()) {
            for (CrossTongBingUserData userData : userDataMap.values()) {
                if (userData.getCampId() == campId) {
                    recalculateBarracksHospitalRecovery(userData.getUserId());
                }
            }
        } else if (buffTypeConfig.getType() == eTongBingBuffType.Type5_CityFoodProduce.getValue()) {
            for (CrossTongBingUserData userData : userDataMap.values()) {
                if (userData.getCampId() == campId) {
                    recalculateCityFoodProdProduction(buff.getCampId(), buffEndTime);
                }
            }
        } else if (buffTypeConfig.getType() == eTongBingBuffType.Type6_ReadySoldierMaxNum.getValue()) {

        }
    }

    private void afterEndBuff(CrossTongBingCampBuff buff) {
        int campId = buff.getCampId();
        TongBingBuffTypeConfig buffTypeConfig = config.getBuffTypeConfigMap().get(buff.getBuffId());
        if (buffTypeConfig.getType() == eTongBingBuffType.Type1_TeamSoldierMaxNum.getValue()) {
            for (CrossTongBingUserData userData : userDataMap.values()) {
                if (userData.getCampId() == campId) {
                    refreshTeam(userData);
                }
            }
        } else if (buffTypeConfig.getType() == eTongBingBuffType.Type2_RecruitSoldierPrice.getValue()) {

        } else if (buffTypeConfig.getType() == eTongBingBuffType.Type3_CityTeamMaxNum.getValue()) {
            synchronized (lock) {
                int addTeamNum = getBuffAdditionParam(buff).get(0);
                for (CrossTongBingCity city : getCrossTongBingData().getCityMap().values()) {
                    if (city.getCampId() == campId) {
                        int num = Math.min(city.getTeamNum(), addTeamNum);
                        city.setTeamNum(city.getTeamNum() - num);
                    }
                }
            }
        } else if (buffTypeConfig.getType() == eTongBingBuffType.Type4_Hospital.getValue()) {
            for (CrossTongBingUserData userData : userDataMap.values()) {
                if (userData.getCampId() == campId) {
                    recalculateBarracksHospitalWoundedMaxNum(userData.getUserId());
                    recalculateBarracksHospitalWoundedCount(userData.getUserId());
                }
            }
        } else if (buffTypeConfig.getType() == eTongBingBuffType.Type5_CityFoodProduce.getValue()) {

        } else if (buffTypeConfig.getType() == eTongBingBuffType.Type6_ReadySoldierMaxNum.getValue()) {
            for (CrossTongBingUserData userData : userDataMap.values()) {
                if (userData.getCampId() == campId) {
                    recalculateBarracksReadyMaxNum(userData.getUserId());
                }
            }
        }
    }

    private void beforeBeginBuff(CrossTongBingCampBuff buff) {
        int campId = buff.getCampId();
        TongBingBuffTypeConfig buffTypeConfig = config.getBuffTypeConfigMap().get(buff.getBuffId());
        if (buffTypeConfig.getType() == eTongBingBuffType.Type1_TeamSoldierMaxNum.getValue()) {

        } else if (buffTypeConfig.getType() == eTongBingBuffType.Type2_RecruitSoldierPrice.getValue()) {

        } else if (buffTypeConfig.getType() == eTongBingBuffType.Type3_CityTeamMaxNum.getValue()) {

        } else if (buffTypeConfig.getType() == eTongBingBuffType.Type4_Hospital.getValue()) {
            for (CrossTongBingUserData userData : userDataMap.values()) {
                if (userData.getCampId() == campId) {
                    recalculateBarracksHospitalRecovery(userData.getUserId());
                }
            }
        } else if (buffTypeConfig.getType() == eTongBingBuffType.Type5_CityFoodProduce.getValue()) {
            for (CrossTongBingUserData userData : userDataMap.values()) {
                if (userData.getCampId() == campId) {
                    recalculateCityFoodProdProduction(campId, buff.getBeginTime());
                }
            }
        } else if (buffTypeConfig.getType() == eTongBingBuffType.Type6_ReadySoldierMaxNum.getValue()) {

        }
    }

    private void afterBeginBuff(CrossTongBingCampBuff buff) {
        int campId = buff.getCampId();
        TongBingBuffTypeConfig buffTypeConfig = config.getBuffTypeConfigMap().get(buff.getBuffId());
        if (buffTypeConfig.getType() == eTongBingBuffType.Type1_TeamSoldierMaxNum.getValue()) {
            for (CrossTongBingUserData userData : userDataMap.values()) {
                if (userData.getCampId() == campId) {
                    refreshTeam(userData);
                }
            }
        } else if (buffTypeConfig.getType() == eTongBingBuffType.Type2_RecruitSoldierPrice.getValue()) {

        } else if (buffTypeConfig.getType() == eTongBingBuffType.Type3_CityTeamMaxNum.getValue()) {
            synchronized (lock) {
                for (CrossTongBingCity city : getCrossTongBingData().getCityMap().values()) {
                    if (city.getCampId() == campId) {
                        city.setTeamNum(city.getTeamNum() + getBuffAdditionParam(buff).get(0));
                        if (city.getBattlefield() != null) {
                            teamToBattlefield(city, true);
                        }
                    }
                }
            }
        } else if (buffTypeConfig.getType() == eTongBingBuffType.Type4_Hospital.getValue()) {
            for (CrossTongBingUserData userData : userDataMap.values()) {
                if (userData.getCampId() == campId) {
                    recalculateBarracksHospitalWoundedMaxNum(userData.getUserId());
                    recalculateBarracksHospitalWoundedCount(userData.getUserId());
                }
            }
        } else if (buffTypeConfig.getType() == eTongBingBuffType.Type5_CityFoodProduce.getValue()) {

        } else if (buffTypeConfig.getType() == eTongBingBuffType.Type6_ReadySoldierMaxNum.getValue()) {
            for (CrossTongBingUserData userData : userDataMap.values()) {
                if (userData.getCampId() == campId) {
                    recalculateBarracksReadyMaxNum(userData.getUserId());
                }
            }
        }
    }

    private long getSkillSoldierAddition(long userId, int occupation) {
        CrossTongBingUserData userData = getUserData(userId);
        List<Long> list = userData.getSoldierAddition();
        long add = 0;
        if (list.size() >= occupation) {
            add = list.get(occupation - 1);
        } else {

        }
        if(list.size() > 5){
            add += list.get(5);
        }
        return add;
    }

    // ============================== 队伍 ==============================

    /**
     * 更新门客数据（没资格也可以更新，不然游戏第一次进来没有值）
     *
     * @param first               首次同步不用设置CD，队伍士兵补满
     * @param isRefreshMaxSoldier 是否只刷新兵力上限
     */
    private int updatePatronsAbility(CrossTongBingUserData userData, boolean first, Map<Integer, CrossTongBingPatrons> patronsMap, List<Long> addition, boolean isRefreshMaxSoldier) {
        synchronized (lock) {

            if (!isRefreshMaxSoldier) {
                if (!first && System.currentTimeMillis() - userData.getLastSyncPatronsTime() < config.getSyncPatronsCd()) {
                    return GameErrorCode.E_TONG_BING_REQ_FREQUENTLY;
                }
                for (CrossTongBingTeam team : userData.getTeamMap().values()) {
                    if (getTeamStatus(team) != eTongBingTeamStatus.Free) {
                        return GameErrorCode.E_TONG_BING_TEAM_NO_FREE;
                    }
                }
            }

            userData.setSoldierAddition(addition);
            userData.setPatronsMap(patronsMap);

            refreshTeam(userData);

            // 只刷新兵力上限不返回兵营
            if (!isRefreshMaxSoldier) {
                // 士兵回兵营
                Map<Integer, CrossTongBingTeam> teamMap = userData.getTeamMap();
                CrossTongBingBarracks barracks = userData.getBarracks();
                for (CrossTongBingTeam team : teamMap.values()) {
                    long soldierNum = team.getSoldierNum();
                    team.setSoldierNum(0);
                    barracks.setReadyNum(barracks.getReadyNum() + soldierNum);
                }

                if (first) {
                    // 【首次同步】
                    // 兵补充满
                    for (CrossTongBingTeam team : userData.getTeamMap().values()) {
                        team.setSoldierNum(team.getMaxSoldier());
                    }
                } else {
                    // 【非首次同步】
                    userData.setLastSyncPatronsTime(System.currentTimeMillis());
                }
            }
        }

        userData.setUpdateOption();

        syncBaseDataMsg(userData);

        return 0;
    }

    /**
     * 刷新队伍主将、兵力
     */
    private void refreshTeam(CrossTongBingUserData userData) {
        Map<Integer, List<CrossTongBingPatrons>> listMap = new ConcurrentHashMap<>();
        for (CrossTongBingPatrons patrons : userData.getPatronsMap().values()) {
            PatronsInfo patronsInfo = PatronsMgr.getPatronsInfo(patrons.getId());
            if (patronsInfo != null) {
                listMap.putIfAbsent(patronsInfo.getOccupation(), new ArrayList<>());
                listMap.get(patronsInfo.getOccupation()).add(patrons);
            }
        }

        for (CrossTongBingTeam team : userData.getTeamMap().values()) {
            List<CrossTongBingPatrons> list = listMap.getOrDefault(team.getOccupation(), new ArrayList<>());
            List<Integer> rankList = list.stream().sorted(Comparator.comparing(CrossTongBingPatrons::getAbility).reversed()).map(CrossTongBingPatrons::getId).collect(Collectors.toList());

            // 重新分配主将副将
            team.setPatronsList(rankList);

            // 重新计算上限
            team.setMaxSoldier(getTeamMaxSoldier(userData, team));
        }

        // 这个变了之后，兵营也会跟这边
        recalculateBarracksReadyMaxNum(userData.getUserId());

        userData.setUpdateOption();

        // 同步队伍数和兵营数据
        syncTeamToSelf(userData.getUserId(), new ArrayList<>(userData.getTeamMap().values()));
        syncBarracksMsg(userData.getUserId());
    }

    private long getTeamMaxSoldier(CrossTongBingUserData userData, CrossTongBingTeam team) {
        // 拿门客赚钱
        long totalAbility = 0L;
        List<Integer> patronsList = team.getPatronsList();
        // 只取前6个统计总赚钱（主将+副将数量=6）
        int count = Math.min(patronsList.size(), config.getTeamMainPatronsNum() + config.getTeamAuxiliaryPatronsNum());
        for (int i = 0; i < count; i++) {
            Integer patronsId = team.getPatronsList().get(i);
            CrossTongBingPatrons patrons = userData.getPatronsMap().get(patronsId);
            if (patrons != null) {
                totalAbility += patrons.getAbility();
            }
        }
        if (totalAbility <= 0) {
            return 0;
        }

        // 后勤部队人数
        int otherPatronsCount = team.getPatronsList().size() - config.getTeamMainPatronsNum() - config.getTeamAuxiliaryPatronsNum();
        if (otherPatronsCount < 0) {
            otherPatronsCount = 0;
        }

        // 【计算】

        BigDecimal paramAbility = BigDecimal.valueOf(totalAbility);
        BigDecimal paramHouQinCount = BigDecimal.valueOf(otherPatronsCount);
        BigDecimal param1 = BigDecimal.valueOf(config.getTeamSoldierMaxNumParam().get(0));
        BigDecimal param2 = BigDecimal.valueOf(config.getTeamSoldierMaxNumParam().get(1));
        BigDecimal param3 = BigDecimal.valueOf(config.getTeamSoldierMaxNumParam().get(2));
        BigDecimal param4 = BigDecimal.valueOf(config.getTeamSoldierMaxNumParam().get(3));
        BigDecimal param5 = BigDecimal.valueOf(config.getTeamSoldierMaxNumParam().get(4));
        BigDecimal one = BigDecimal.ONE;

        // log x param2
        BigDecimal logPart = BigDecimal.valueOf(
                Math.log10(paramAbility.divide(param1, 8, BigDecimal.ROUND_DOWN).doubleValue())
        ).multiply(param2).setScale(8, BigDecimal.ROUND_DOWN);

        // 1 + 后勤 x 参数3/4
        BigDecimal houQinPart = one.add(
                paramHouQinCount.multiply(
                        param3.divide(param4, 8, BigDecimal.ROUND_DOWN)
                )
        );

        // 左边一大串
        BigDecimal leftPart = logPart.multiply(houQinPart);

        // 取max
        if (leftPart.compareTo(param5) < 0) {
            leftPart = param5;
        }

        // 拿buff加成
        int buffAddition = getBuffAddition(userData.getUserId(), eTongBingBuffType.Type1_TeamSoldierMaxNum).get(0);
        long skillSoldierAddition = getSkillSoldierAddition(userData.getUserId(), team.getOccupation());

        // 加buff加成
        leftPart = leftPart
                .add(BigDecimal.valueOf(skillSoldierAddition))
                .add(BigDecimal.valueOf(buffAddition));

        // 返回
        return leftPart.setScale(0, BigDecimal.ROUND_DOWN).longValue();
    }

    private CrossTongBingTeam getTeamByArrive(CrossTongBingTeamArrive arrive) {
        return getUserData(arrive.getUserId()).getTeamMap().get(arrive.getTeamId());
    }

    private eTongBingTeamStatus getTeamStatus(CrossTongBingTeam team) {
        if (team.getRouteList().size() > 0) {
            return eTongBingTeamStatus.Move;
        }
        if (team.getCityId() == 0) { // 未分配阵营时为0
            return eTongBingTeamStatus.Free;
        }

        // 在主城时，都为空闲
        int campId = getUserData(team.getUserId()).getCampId();
        TongBingCityConfig cityConfig = config.getCityConfigMap().get(team.getCityId());
        if (cityConfig.getType() == 1 && cityConfig.getInitialCamp() == campId) {
            return eTongBingTeamStatus.Free;
        }

        CrossTongBingCity city = getCrossTongBingData().getCityMap().get(team.getCityId());
        if (city.getBattlefield() != null) {
            return eTongBingTeamStatus.Battle;
        }
        return eTongBingTeamStatus.Free;
    }

    private List<CrossTongBingTeam> getTopTeam() {
        List<CrossTongBingTeam> result = new ArrayList<>();

        int campTopNum = config.getLightRankNum();

        for (CrossTongBingRankListModel rankListModel : campUserRankMap.values()) {
            int num = 0;
            for (CrossTongBingRank rank : rankListModel.getRankList()) {
                if (++num > campTopNum) {
                    break;
                }
                CrossTongBingUserData userData = getUserData(Long.valueOf(rank.getId()));
                for (CrossTongBingTeam team : userData.getTeamMap().values()) {
                    if (getTeamStatus(team) == eTongBingTeamStatus.Move) {
                        result.add(team);
                    }
                }
            }
        }

        return result;
    }

    private int needHelp(long userId, TongBingProto.TongBingNeedHelpReqMsg reqMsg) {
        int cityId = reqMsg.getCityId();

        CrossTongBingCity city = getCrossTongBingData().getCityMap().get(cityId);
        if (city == null) {
            return GameErrorCode.E_TONG_BING_CITY_NO_EXIST;
        }

        CrossTongBingUserData userData = getUserData(userId);
        if (!isAble(userData)) {
            return GameErrorCode.E_TONG_BING_NO_ABLE;
        }

        synchronized (lock) {

            long now = System.currentTimeMillis();

            // 冷却
            if (now - userData.getNeedHelpTime() < config.getNeedHelpCd()) {
                return GameErrorCode.E_TONG_BING_NEED_HELP_ON_CD;
            }

            // 是否战斗中
            CrossTongBingBattlefield battlefield = city.getBattlefield();
            if (battlefield == null) {
                return GameErrorCode.E_TONG_BING_NO_BATTLE;
            }

            // 看看有没有自己的阵营在里面
            int myCampId = userData.getCampId();
            boolean hasMyCamp = false;
            if (city.getCampId() == myCampId) {
                hasMyCamp = true;
            }
            if (!hasMyCamp) {
                for (CrossTongBingTeamArrive arrive : new LinkedList<>(city.getAtkTeamList())) {
                    if (getUserData(arrive.getUserId()).getCampId() == myCampId) {
                        hasMyCamp = true;
                        break;
                    }
                }
            }
            if (!hasMyCamp) {
                for (CrossTongBingBattlefieldPart part : battlefield.getPartMap().values()) {
                    for (CrossTongBingBattlefieldTeam tempTeam : new LinkedList<>(part.getAtkList())) {
                        if (myCampId == getUserData(tempTeam.getTeam().getUserId()).getCampId()) {
                            hasMyCamp = true;
                            break;
                        }
                    }
                }
            }
            if (!hasMyCamp) {
                return GameErrorCode.E_TONG_BING_NO_MY_CAMP_TEAM;
            }

            // 求援
            battlefield.setHelpNum(battlefield.getHelpNum() + 1);

            // 设置CD
            userData.setNeedHelpTime(now);

            // 同步城池
            addNeedSyncCity(cityId);
        }

        // 同步求援消息
        addSystemChat(eMessageType.TongBingCampHelpChat, userId, userData.getCampId(),
                (userData.getCampId() != city.getCampId() ? 1 : 2) + GameConfig.TALK_SEPARATOR + String.valueOf(city.getCityId()),
                System.currentTimeMillis());

        return 0;
    }

    // ============================== CMD ==============================

    public void loginByCmd(long userId, TongBingProto.CrossTongBingLoginReqMsg reqMsg) {
        CrossTongBingUserData userData = getUserData(userId);
        // 第一次帮他同步
        if (userData.getPatronsMap().isEmpty()) {
            updatePatronsAbility(userData, true, CrossTongBingPb.parseCrossTongBingPatrons(reqMsg.getPatronsListList()), reqMsg.getSoldierAdditionList(), false);
        }
        // 同步数据
        syncBaseDataMsg(userData);
        // 跨服同步用户数据到游戏服
        syncUserDataToGameServer(userData);
        //任命了别人别人可能没在线，登录的时候再同步一次
        syncUserPosition(userId);
        // 同步战功成就
        notifyConditionChange(userId, eGamePlayerEventType.TongBingZhanGong, userData.getZhanGong());
        // 同步全部红点
        if (config.inEnterActivityTime()) {
            TongBingProto.TongBingSyncRedDotRespMsg.Builder builder = TongBingProto.TongBingSyncRedDotRespMsg.newBuilder();
            builder.addAllType(userData.getRedDotMap().keySet());
            sendPacket(ClientProtocol.U_TONG_BING_SYNC_RED_DOT, builder, userData.getUserId());
        }
        // 同步高亮玩家
        syncLight();
        // 同步阵营指挥官
        syncCommanderInfo(userId, userData.getCampId());
    }

    private void syncUserPosition(long userId) {
        UserBaseInfo userBaseInfo = CrossUserMgr.getUserBaseInfo(userId);
        if (userBaseInfo != null) {
            CrossTongBingUserData userData = getUserData(userId);
            if (isAble(userData) && userData.getPositionId() != eTongBingPosition.None.getValue()) {
                TongBingProto.CrossTongBingCityWinerAppointRespMsg.Builder crespMsg = TongBingProto.CrossTongBingCityWinerAppointRespMsg.newBuilder();
                crespMsg.setWinerName("");
                crespMsg.setPosition(userData.getPositionId());
                crespMsg.setSendReward(false);
                MessageHelper.sendPacket(userBaseInfo.getServerId(), userId, YanQuMessageUtils.buildMessage(GameProtocol.S_CROSS_TONG_BING_CITY_APPOINT_POSITION, crespMsg));
            }
        }
    }

    //同步变化
    private void syncServerNpcEvent(Map<Long, Map<Long, Map<Integer, Integer>>> serverSyncMap) {
        for (long serverId : serverSyncMap.keySet()) {
            TongBingProto.CrossTongBingEventSyncMsg.Builder syncMsg = TongBingProto.CrossTongBingEventSyncMsg.newBuilder();
            Map<Long, Map<Integer, Integer>> userMap = serverSyncMap.get(serverId);
            for (long userId : userMap.keySet()) {
                Map<Integer, Integer> eventMap = userMap.get(userId);
                TongBingProto.CrossTongBingEventTempMsg.Builder userEventMsg = TongBingProto.CrossTongBingEventTempMsg.newBuilder();
                userEventMsg.setUserId(userId);
                for (int eventPos : eventMap.keySet()) {
                    TongBingProto.TongBingEventMsg.Builder eventMsg = TongBingProto.TongBingEventMsg.newBuilder();
                    eventMsg.setPositionNo(eventPos);
                    eventMsg.setEventId(eventMap.get(eventPos));
                    userEventMsg.addEvents(eventMsg);
                }
                syncMsg.addEvents(userEventMsg);
            }
            MessageHelper.sendPacket(serverId, 0, YanQuMessageUtils.buildMessage(GameProtocol.S_CROSS_TONG_BING_NPC_EVENT_SYNC, syncMsg));
        }
    }

    public void unionJoinByCmd(long userId, TongBingProto.CrossTongBingJoinReqMsg reqMsg) {
        int ret = unionJoin(userId, reqMsg);
        TongBingProto.TongBingJoinRespMsg.Builder builder = TongBingProto.TongBingJoinRespMsg.newBuilder();
        builder.setRet(ret);
        sendPacket(ClientProtocol.U_TONG_BING_JOIN, builder, userId);
    }

    public void getUnionJoinByCmd(long userId, TongBingProto.CrossTongBingGetJoinUnionReqMsg reqMsg) {

        // 1报名，2入选（似乎没用了）
        int reqType = reqMsg.getReq().getType();

        // 拿榜单排序
        LinkedList<CrossTongBingUnionJoin> list = new LinkedList<>(unionJoinMap.values());
        list.sort(new Comparator<CrossTongBingUnionJoin>() {
            @Override
            public int compare(CrossTongBingUnionJoin o1, CrossTongBingUnionJoin o2) {
                return Long.compare(o2.getTotalAbility(), o1.getTotalAbility());
            }
        });

        // 拿自己商会
        String unionUid = "";
        if (getCrossTongBingData().isLockUnion()) {
            unionUid = getUserData(userId).getUnionUid();
        }
        if ("".equals(unionUid)) {
            for (CrossTongBingUnionJoin unionJoin : unionJoinMap.values()) {
                if (unionJoin.getMemberMap().containsKey(userId)) {
                    unionUid = unionJoin.getUnionUid();
                    break;
                }
            }
        }

        int curRank = 0;
        CrossTongBingUnionJoin myJoin = null;
        int myRank = -1;
        int maxRank = config.getUnionNum();

        TongBingProto.TongBingGetJoinUnionRespMsg.Builder builder = TongBingProto.TongBingGetJoinUnionRespMsg.newBuilder();
        builder.setRet(0);
        builder.setType(reqType);
        for (CrossTongBingUnionJoin join : list) {
            curRank++;
            if (curRank <= maxRank) {
                RankProto.UnionRankTempMsg.Builder tmp = RankProto.UnionRankTempMsg.newBuilder();
                tmp.setUnionUid(join.getUnionUid());
                tmp.setUnionBaseData(UnionBasePb.parseUnionBaseTempMsg(join.getUnionBaseInfo()));
                tmp.setValue(String.valueOf(join.getTotalAbility()));
                builder.addUnionRank(tmp);
            }
            if (join.getUnionUid().equals(unionUid)) {
                myJoin = join;
                myRank = curRank;
            }
        }
        // 商会未入选但是报名了,需要显示商会的商会排名、商会总赚速
        if (myJoin == null) {
            myJoin = getUnionJoin(unionUid);
        }
        if (myJoin != null) {
            builder.setMyRank(myRank);
            if (myJoin.getTotalAbility() == 0L) {
                myJoin.reCalcAbility();
            }
            builder.setValue(String.valueOf(myJoin.getTotalAbility()));
        }
        sendPacket(ClientProtocol.U_TONG_BING_GET_JOIN_UNION, builder, userId);
    }

    public void updateUnionByCmd(TongBingProto.CrossTongBingGetUnionRespMsg reqMsg) {

        List<Long> syncList = new ArrayList<>();

        synchronized (lock) {

            // 时间
            boolean lockUnion = getCrossTongBingData().isLockUnion();
            boolean activityEnd = System.currentTimeMillis() > config.getActivityInfo().getEndTime() * 1000L;

            // 结束了就无法操作了
            if (activityEnd) {
                return;
            }

            // 自动报名数据（这里还是按赚速，拿不到最高赚钱）
            boolean autoJoin = reqMsg.getType() == 2;
            List<TongBingProto.TongBingUnionMsg> rankList = reqMsg.getUnionList().stream().sorted(new Comparator<TongBingProto.TongBingUnionMsg>() {
                @Override
                public int compare(TongBingProto.TongBingUnionMsg o1, TongBingProto.TongBingUnionMsg o2) {
                    BigInteger bigInt1 = new BigInteger(o1.getUnionInfo().getTotalEarnSpeed());
                    BigInteger bigInt2 = new BigInteger(o2.getUnionInfo().getTotalEarnSpeed());
                    return bigInt2.compareTo(bigInt1);
                }
            }).collect(Collectors.toList());

            // 遍历数据一个个处理
            for (TongBingProto.TongBingUnionMsg unionMsg : rankList) {

                // 【商会处理】
                CrossTongBingUnionJoin join = unionJoinMap.get(unionMsg.getUnionUid());
                if (join == null) {
                    // 自动报名 && 未锁名单 && 有空位
                    if (autoJoin && !lockUnion && unionJoinMap.size() < config.getUnionNum()) {
                        join = new CrossTongBingUnionJoin();
                        join.setActivityId(config.getActivityInfo().getActivityId());
                        join.setGroupId(groupId);
                        join.setUnionUid(unionMsg.getUnionUid());
                        join.setUnionBaseInfo(UnionBasePb.parseUnionBaseInfo(unionMsg.getUnionInfo()));
                        join.setMemberMap(new ConcurrentHashMap<>());
                        join.setInsertOption();
                        unionJoinMap.put(join.getUnionUid(), join);
                        // 日志
                        CrossAutoLogMgr.add(new LogTongBingJoin(
                                config.getActivityInfo().getActivityId(),
                                -1,
                                unionMsg.getUnionUid(),
                                1,
                                groupId
                        ));
                    }
                }
                if (join == null) {
                    continue;
                }

                // 【成员处理】
                Map<Long, CrossTongBingUnionJoinMember> oldMap = join.getMemberMap();
                if (!lockUnion) {
                    Map<Long, CrossTongBingUnionJoinMember> memberMap = new ConcurrentHashMap<>();
                    for (TongBingProto.TongBingUnionMemberInfoMsg msg : unionMsg.getMemberList()) {
                        CrossTongBingUnionJoinMember member = oldMap.remove(msg.getUserId());
                        if (member == null) {
                            member = new CrossTongBingUnionJoinMember(msg.getUserId(), msg.getServerId());
                            syncList.add(member.getUserId()); // 添加报名状态同步
                        }
                        member.setPosition(msg.getPosition());
                        memberMap.put(member.getUserId(), member);
                    }
                    join.setMemberMap(memberMap);
                    syncList.addAll(oldMap.keySet()); // 添加报名状态同步
                } else {
                    for (TongBingProto.TongBingUnionMemberInfoMsg msg : unionMsg.getMemberList()) {
                        CrossTongBingUnionJoinMember member = oldMap.get(msg.getUserId());
                        if (member != null) {
                            member.setPosition(msg.getPosition());
                        }
                    }
                }
                join.setUpdateOption();
            }
        }

        // 同步报名状态
        for (Long userId : syncList) {
            syncBaseDataMsg(getUserData(userId));
        }

        // 没赚钱的拉一下
        if (!getCrossTongBingData().isLockUnion()) {
            for (TongBingProto.TongBingUnionMsg unionMsg : reqMsg.getUnionList()) {
                checkUnionJoinMember(unionMsg.getUnionUid());
            }
        }

    }

    public void updateUnionMemberByCmd(TongBingProto.CrossTongBingGetMemberRespMsg reqMsg) {
        synchronized (lock) {
            if (getCrossTongBingData().isLockUnion()) {
                return;
            }

            for (TongBingProto.TongBingUnionMemberMsg memberMsg : reqMsg.getMemberList()) {
                CrossTongBingUnionJoin join = unionJoinMap.get(memberMsg.getUnionUid());
                if (join == null || !join.getMemberMap().containsKey(memberMsg.getUserId())) {
                    continue;
                }

                CrossTongBingUnionJoinMember member = join.getMemberMap().get(memberMsg.getUserId());
                member.setEarnSpeed(Long.valueOf(memberMsg.getEarnSpeed()));
                if (memberMsg.hasPatronsTopAbility() && Long.valueOf(memberMsg.getPatronsTopAbility()) > member.getTopAbility()) {
                    member.setTopAbility(Long.valueOf(memberMsg.getPatronsTopAbility()));
                    join.reCalcAbility();
                }
                join.setUpdateOption();
            }
        }
    }

    public void notifyUnionMemberChangeByCmd(String unionUid) {
        // 没报名，不管
        CrossTongBingUnionJoin unionJoin = getUnionJoin(unionUid);
        if (unionJoin == null) {
            return;
        }
        // 结束了，不管
        if (System.currentTimeMillis() > config.getActivityInfo().getEndTime() * 1000L) {
            return;
        }
        // 重新拉一下商会成员数据
        sendGetUnionMsg(1, Collections.singletonList(unionUid), 0);
    }

    public void syncPatronsByCmd(TongBingProto.CrossTongBingUpdatePatronsReqMsg reqMsg) {
        Map<Integer, CrossTongBingPatrons> patronsMap = CrossTongBingPb.parseCrossTongBingPatrons(reqMsg.getPatronsList());
        CrossTongBingUserData userData = getUserData(reqMsg.getUserId());
        boolean isRefreshMaxSoldier = reqMsg.getIsRefreshMaxSoldier();
        int ret = updatePatronsAbility(userData, false, patronsMap, reqMsg.getSoldierAdditionList(), isRefreshMaxSoldier);

        TongBingProto.TongBingUpdatePatronsAbilityRespMsg.Builder respMsg = TongBingProto.TongBingUpdatePatronsAbilityRespMsg.newBuilder();
        respMsg.setRet(ret);
        for (CrossTongBingTeam team : userData.getTeamMap().values()) {
            respMsg.addTeam(parseTongBingTeamMsg(userData, team));
        }
        sendPacket(ClientProtocol.U_TONG_BING_UPDATE_PATRONS_ABILITY, respMsg, reqMsg.getUserId());
    }

    /**
     * 设置战略目标
     */
    public void setTargetCityByCmd(long userId, TongBingProto.TongBingSetTargetCityReqMsg reqMsg) {
        int ret = setTargetCity(userId, reqMsg);
        CrossTongBingUserData userData = getUserData(userId);
        TongBingProto.TongBingSetTargetCityRespMsg.Builder builder = TongBingProto.TongBingSetTargetCityRespMsg.newBuilder();
        builder.setRet(ret);
        CrossTongBingTeam team = userData.getTeamMap().get(reqMsg.getOccupation());
        if (team != null) {
            builder.setTeam(parseTongBingTeamMsg(userData, team));
        }
        sendPacket(ClientProtocol.U_TONG_BING_SET_TARGET_CITY, builder, userId);
    }

    /**
     * 设置战略目标
     */
    public int setTargetCity(long userId, TongBingProto.TongBingSetTargetCityReqMsg reqMsg) {
        // 参数校验
        CrossTongBingUserData userData = getUserData(userId);
        if (!isAble(userData)) {
            return GameErrorCode.E_TONG_BING_NO_ABLE;
        }
        int occupation = reqMsg.getOccupation();
        int cityId = reqMsg.getCityId();
        CrossTongBingTeam team = userData.getTeamMap().get(occupation);
        if (team == null) {
            return GameErrorCode.E_TONG_BING_TEAM_NO_EXIST;
        }

        // 时间判断(活动期和休战期可操作)
        eTongBingStage stage = this.config.getTongBingStage();
        if (stage != eTongBingStage.Stage1Time && stage != eTongBingStage.Stage1RestTime) {
            return GameErrorCode.E_TONG_BING_NO_OPERATE_TIME;
        }
        // 判断是否可达
        if (cityId != 0) {
            List<Integer> cityRouteList = getRouteList(team, cityId);
            if (cityRouteList == null || cityRouteList.size() <= 1) {
                return GameErrorCode.E_TONG_BING_NO_ROUTE;
            }
        }
        // 设置战略目标
        team.setTargetCityId(cityId);
        userData.setUpdateOption();
        return 0;
    }

    public void moveTeamByCmd(long userId, TongBingProto.TongBingMoveTeamReqMsg reqMsg) {

        int ret = moveToCity(userId, reqMsg);

        CrossTongBingUserData userData = getUserData(userId);

        // 第一次发移动请求，都当他引导了
        if (userData.getGuide() == 0) {
            userData.setGuide(1);
            syncBaseDataMsg(userData);
        }

        TongBingProto.TongBingMoveTeamRespMsg.Builder builder = TongBingProto.TongBingMoveTeamRespMsg.newBuilder();
        builder.setRet(ret);
        for (Integer occu : reqMsg.getOccupationList()) {
            CrossTongBingTeam team = userData.getTeamMap().get(occu);
            if (team != null) {
                builder.addTeam(parseTongBingTeamMsg(userData, team));
            }
        }

        sendPacket(ClientProtocol.U_TONG_BING_MOVE_TEAM, builder, userId);
    }

    public void speedUpByCmd(long userId, TongBingProto.TongBingMoveSpeedUpReqMsg reqMsg) {
        int occupation = reqMsg.getOccupation();
        int times = reqMsg.getTimes();

        int ret = speedUp(userId, occupation, times);
        if (ret != 0) {
            CrossTongBingUserData userData = getUserData(userId);
            CrossTongBingTeam team = userData.getTeamMap().get(occupation);

            TongBingProto.TongBingMoveSpeedUpRespMsg.Builder resp = TongBingProto.TongBingMoveSpeedUpRespMsg.newBuilder();
            resp.setRet(ret);
            if (team != null) {
                resp.setTeam(parseTongBingTeamMsg(userData, team));
            }

            TongBingProto.CrossTongBingMoveSpeedUpRespMsg.Builder builder = TongBingProto.CrossTongBingMoveSpeedUpRespMsg.newBuilder();
            builder.setResp(resp);
            builder.setReqTimes(times);
            sendPacket(GameProtocol.S_TONG_BING_MOVE_SPEED_UP_FROM_CROSS, builder, userId);

            // 发光玩家信息
            syncLight();
        }
    }

    public void getDataByCmd(long userId) {
        TongBingProto.TongBingGetDataRespMsg.Builder builder = TongBingProto.TongBingGetDataRespMsg.newBuilder();

        CrossTongBingUserData userData = getUserData(userId);
        if (!isAble(userData)) {
            builder.setRet(GameErrorCode.E_TONG_BING_NO_ABLE);
        } else {
            builder.setRet(0);
            for (CrossTongBingTeam team : userData.getTeamMap().values()) {
                builder.addTeam(parseTongBingTeamMsg(userData, team));
            }
            for (CrossTongBingCity city : getCrossTongBingData().getCityMap().values()) {
                builder.addCity(parseTongBingCityMsg(city));
            }
            builder.setBarracks(parseTongBingBarracksMsg(userId));
            for (CrossTongBingTeam team : getTopTeam()) {
                builder.addTeam(parseTongBingTeamMsg(getUserData(team.getUserId()), team)); // 前几名也加到这边
            }
            for (CrossTongBingCityEvent event : getCrossTongBingData().getCityEventMap().values()) {
                builder.addEvent(parseTongBingCityEventMsg(event));
            }
            List<CrossTongBingCampBuff> buffList = getCrossTongBingData().getCampBuffMap().get(userData.getCampId());
            if (buffList != null) {
                for (CrossTongBingCampBuff buff : new ArrayList<>(buffList)) {
                    builder.addBuff(parseTongBingBuffMsg(buff));
                }
            }
            for (Map.Entry<Integer, Integer> entry : userData.getEventMap().entrySet()) {
                TongBingProto.TongBingEventMsg.Builder eventMsg = TongBingProto.TongBingEventMsg.newBuilder();
                eventMsg.setPositionNo(entry.getKey());
                eventMsg.setEventId(entry.getValue());
                builder.addNpcEvent(eventMsg);
            }
        }

        sendPacket(ClientProtocol.U_TONG_BING_GET_DATA, builder, userId);
    }

    public void getMyTeamPosByCmd(long userId, TongBingProto.TongBingGetBattlefieldMyTeamReqMsg reqMsg) {
        int cityId = reqMsg.getCityId();

        // 返回值
        TongBingProto.TongBingGetBattlefieldMyTeamRespMsg.Builder builder = TongBingProto.TongBingGetBattlefieldMyTeamRespMsg.newBuilder();

        // 判断资格
        int campId = getUserData(userId).getCampId();
        if (campId <= 0) {
            builder.setRet(GameErrorCode.E_TONG_BING_NO_ABLE);
            sendPacket(ClientProtocol.U_TONG_BING_GET_MY_TEAM_POS, builder, userId);
            return;
        }

        builder.setRet(0);

        CrossTongBingCity city = getCrossTongBingData().getCityMap().get(cityId);
        if (city != null) {
            CrossTongBingBattlefield battlefield = city.getBattlefield();
            if (battlefield != null) {

                // 战场内的
                for (CrossTongBingBattlefieldPart part : battlefield.getPartMap().values()) {
                    LinkedList<CrossTongBingBattlefieldTeam> list = city.getCampId() == campId ? part.getDefList() : part.getAtkList();
                    int no = 0;
                    for (CrossTongBingBattlefieldTeam team : new LinkedList<>(list)) {
                        no++;
                        if (team.getTeam() != null && team.getTeam().getUserId() == userId) {
                            builder.addTeamPos(TongBingProto.TongBingBattlefieldMyTeamMsg.newBuilder()
                                    .setPartId(part.getPartId())
                                    .setTeamId(team.getTeam().getTeamId())
                                    .setPos(no));
                        }
                    }
                }

                // 加一下排队中的
                LinkedList<CrossTongBingTeamArrive> arriveLinkedList = city.getCampId() == campId ? city.getDefTeamList() : city.getAtkTeamList();
                int no = 0;
                for (CrossTongBingTeamArrive arrive : new LinkedList<>(arriveLinkedList)) {
                    no++;
                    if (arrive.getUserId() == userId) {
                        builder.addTeamPos(TongBingProto.TongBingBattlefieldMyTeamMsg.newBuilder()
                                .setPartId(0)
                                .setTeamId(arrive.getTeamId())
                                .setPos(no));
                    }
                }
            }
        }

        sendPacket(ClientProtocol.U_TONG_BING_GET_MY_TEAM_POS, builder, userId);
    }

    public void addUserWarReport(CrossTongBingUserWarReport report) {
        synchronized (lock) {
            int size = needInsertUserReportList.size();
            int no = size == 0 ? 1 : needInsertUserReportList.get(size - 1).getNo();
            report.setNo(no + 1);
            userWarReportsList.add(report);
            needInsertUserReportList.add(report);
        }

        // 红点
        if (report.getUserIdDef() != 0) {
            addRedDot(getUserData(report.getUserIdDef()), eTongBingRedDotType.Type2_UserWarReport);
        }
        addRedDot(getUserData(report.getUserIdAct()), eTongBingRedDotType.Type2_UserWarReport);

    }

    public void addCityWarReport(CrossTongBingCityWarReport report) {
        synchronized (lock) {
            cityWarReportList.add(report);
            needInsertCityReportList.add(report);
        }

        // 红点
        for (Integer campId : report.getAtkCampId()) {
            for (CrossTongBingUserData userData : userDataMap.values()) {
                if (userData.getCampId() == campId) {
                    addRedDot(userData, eTongBingRedDotType.Type3_CityWarReport);
                }
            }
        }
        if (report.getDefCampId() != 0) {
            for (CrossTongBingUserData userData : userDataMap.values()) {
                if (userData.getCampId() == report.getDefCampId()) {
                    addRedDot(userData, eTongBingRedDotType.Type3_CityWarReport);
                }
            }
        }
    }

    //获得城池战报
    public void cityWarReport(long userId) {
        CrossTongBingUserData userData = getUserData(userId);
        TongBingProto.TongBingCityWarReportRespMsg.Builder respMsg = TongBingProto.TongBingCityWarReportRespMsg.newBuilder();
        respMsg.setRet(0);
        List<CrossTongBingCityWarReport> list = new ArrayList<>(cityWarReportList);
        int count = 0;
        for (int i = list.size() - 1; i >= 0; i--) {
            CrossTongBingCityWarReport report = list.get(i);
            if (count >= config.getUserReportNum()) {
                break;
            }
            if (report.getDefCampId() == userData.getCampId() || report.getAtkCampId().contains(userData.getCampId())) {
                count++;
                TongBingProto.TongBingCityWarReportMsg.Builder tempMsg = TongBingProto.TongBingCityWarReportMsg.newBuilder();
                for (int atkCampId : report.getAtkCampId()) {
                    tempMsg.addAtkCampId(atkCampId);
                }
                tempMsg.setDefCampId(report.getDefCampId());
                tempMsg.setType(report.getType());
                tempMsg.setCityId(report.getCityId());
                tempMsg.setTime(report.getTime());
                for (CrossTongBingBattlefieldRank atkRank : report.getAtkRankList()) {
                    tempMsg.addAtkRank(parseTongBingBattlefieldRankMsg(atkRank));
                }
                for (CrossTongBingBattlefieldRank defRank : report.getDefRankList()) {
                    tempMsg.addDefRank(parseTongBingBattlefieldRankMsg(defRank));
                }
                tempMsg.setWinCampId(report.getWinCampId());
                respMsg.addList(tempMsg);
            }
        }
        sendPacket(ClientProtocol.U_TONG_BING_CITY_WAR_REPORT, respMsg, userId);
    }

    //获得个人战报
    public void userWarReport(long userId) {
        TongBingProto.TongBingUserWarReportRespMsg.Builder respMsg = TongBingProto.TongBingUserWarReportRespMsg.newBuilder();
        respMsg.setRet(0);
        List<CrossTongBingUserWarReport> list = new ArrayList<>(userWarReportsList);
        UserBaseInfo userBaseInfo = CrossUserMgr.getUserBaseInfo(userId);
        String myNickName = "";
        if (userBaseInfo != null) {
            myNickName = userBaseInfo.getNickName();
        }
        int count = 0;
        for (int i = list.size() - 1; i >= 0; i--) {
            CrossTongBingUserWarReport report = list.get(i);
            if (count >= config.getCityReportNum()) {
                break;
            }
            if (report.getUserIdAct() == userId) {
                count++;
                TongBingProto.TongBingUserWarReportMsg.Builder tempMsg = TongBingProto.TongBingUserWarReportMsg.newBuilder();
                tempMsg.setNo(report.getNo());
                tempMsg.setMyPatronsId(report.getActPatronsId());
                tempMsg.setMySoldierNum(report.getActSoldierNum());
                tempMsg.setOtherPatronsId(report.getDefPatronsId());
                tempMsg.setOtherSoldierNum(report.getDefSoldierNum());
                tempMsg.setTime(report.getTime());
                tempMsg.setAddZhanGong(report.getActZhanGong());
                tempMsg.setKillSoldierNum(report.getDefDieSoldierNum());
                tempMsg.setCityId(report.getCityId());
                tempMsg.setIsWin(report.getWinUserId() == 2);
                tempMsg.setCostSoldierNum(report.getActDieSoldierNum());
                tempMsg.setMyAddition(report.getActAddition());
                tempMsg.setOtherAddition(report.getDefAddition());
                tempMsg.setMyNickName(myNickName);
                String otherNickName = "";
                if (report.getUserIdDef() != 0) {
                    UserBaseInfo otherUserBaseInfo = CrossUserMgr.getUserBaseInfo(report.getUserIdDef());
                    if (otherUserBaseInfo != null) {
                        otherNickName = otherUserBaseInfo.getNickName();
                    }
                }
                tempMsg.setOtherNickName(otherNickName);
                respMsg.addList(tempMsg);
            } else if (report.getUserIdDef() == userId) {
                count++;
                TongBingProto.TongBingUserWarReportMsg.Builder tempMsg = TongBingProto.TongBingUserWarReportMsg.newBuilder();
                tempMsg.setNo(report.getNo());
                tempMsg.setMyPatronsId(report.getDefPatronsId());
                tempMsg.setMySoldierNum(report.getDefSoldierNum());
                tempMsg.setOtherPatronsId(report.getActPatronsId());
                tempMsg.setOtherSoldierNum(report.getActSoldierNum());
                tempMsg.setTime(report.getTime());
                tempMsg.setAddZhanGong(report.getDefZhanGong());
                tempMsg.setKillSoldierNum(report.getActDieSoldierNum());
                tempMsg.setCityId(report.getCityId());
                tempMsg.setIsWin(report.getWinUserId() != 2);
                tempMsg.setCostSoldierNum(report.getDefDieSoldierNum());
                tempMsg.setMyAddition(report.getDefAddition());
                tempMsg.setOtherAddition(report.getActAddition());
                tempMsg.setMyNickName(myNickName);
                String otherNickName = "";
                if (report.getUserIdAct() != 0) {
                    UserBaseInfo otherUserBaseInfo = CrossUserMgr.getUserBaseInfo(report.getUserIdAct());
                    if (otherUserBaseInfo != null) {
                        otherNickName = otherUserBaseInfo.getNickName();
                    }
                }
                tempMsg.setOtherNickName(otherNickName);
                respMsg.addList(tempMsg);
            }
        }
        sendPacket(ClientProtocol.U_TONG_BING_USER_WAR_REPORT, respMsg, userId);
    }

    //活动阶段2城主等信息
    public void getCityWinner(long userId) {
        TongBingProto.TongBingCityWinerRespMsg.Builder respMsg = TongBingProto.TongBingCityWinerRespMsg.newBuilder();
        respMsg.setRet(0);
        long winnerUserId = crossTongBingData.getProcess2WinUserId();
        for (CrossTongBingUserData userData : userDataMap.values()) {

            boolean needSync = userData.getPositionId() != 0; // 有职位，必须发
            if (userId == winnerUserId) { // 自己是城主，就要所有人的信息
                needSync = true;
            }

            if (!needSync) {
                continue;
            }

            UserBaseInfo userBaseInfo = CrossUserMgr.getUserBaseInfo(userData.getUserId());
            if (userBaseInfo != null) {
                TongBingProto.TongBingUserPositionMsg.Builder userTempMsg = TongBingProto.TongBingUserPositionMsg.newBuilder();
                userTempMsg.setUserId(userData.getUserId());
                userTempMsg.setCampId(userData.getCampId());
                userTempMsg.setZhangong(userData.getZhanGong());
                userTempMsg.setPlayerBaseData(PlayerBasePb.parsePlayerBaseTempMsg(userBaseInfo));
                userTempMsg.setTotalPosSilver(userData.getTotalPosSilver().toString());
                if (winnerUserId == userData.getUserId()) {
                    userTempMsg.setPosition(eTongBingPosition.Master.getValue());
                } else {
                    userTempMsg.setPosition(userData.getPositionId());
                }
                respMsg.addList(userTempMsg);
            }
        }
        sendPacket(ClientProtocol.U_TONG_BING_CITY_WINNER, respMsg, userId);
    }

    //获取玩家的赚速，任命职位的时候扣别人的赚速加到自己身上就是用这个赚速
    private BigInteger getUserEarnSpeed(long userId) {
        CrossTongBingUserData userData = getUserData(userId);
        if (userData == null) {
            return BigInteger.ZERO;
        }
        if (unionJoinMap.containsKey(userData.getUnionUid())) {
            Map<Long, CrossTongBingUnionJoinMember> joinMemberMap = unionJoinMap.get(userData.getUnionUid()).getMemberMap();
            if (joinMemberMap.containsKey(userId)) {
                return BigInteger.valueOf(joinMemberMap.get(userId).getEarnSpeed());
            }
        }
        return BigInteger.ZERO;
    }

    public void cityWinnerAppointPosition(long userId, TongBingProto.CrossTongBingCityWinerAppointReqMsg reqMsg) {
        TongBingProto.TongBingCityWinerRespMsg.Builder respMsg = TongBingProto.TongBingCityWinerRespMsg.newBuilder();
        respMsg.setRet(0);
        long winnerUserId = crossTongBingData.getProcess2WinUserId();
        if (userId != winnerUserId) {
            respMsg.setRet(GameErrorCode.E_TONG_BING_APPOINT_NOT_WINNER);
            sendPacket(ClientProtocol.U_TONG_BING_CITY_WINNER_APPOINT, respMsg, userId);
            return;
        }
        CrossTongBingUserData winnerUserData = getUserData(winnerUserId);
        CrossTongBingUserData userData = getUserData(reqMsg.getUserId());//任命的玩家
        synchronized (lock) {
            Map<Integer, TongBingTitleConfig> titleConfigMap = config.getTitleConfigMap();
            if (!titleConfigMap.containsKey(reqMsg.getPosition())) {
                respMsg.setRet(GameErrorCode.E_TONG_BING_USER_POSITION_APPOINT_ERR);
                sendPacket(ClientProtocol.U_TONG_BING_CITY_WINNER_APPOINT, respMsg, userId);
                return;
            }
            TongBingTitleConfig titleConfig = titleConfigMap.get(reqMsg.getPosition());
            if (userData.getPositionId() != eTongBingPosition.None.getValue()) {
                respMsg.setRet(GameErrorCode.E_TONG_BING_USER_POSITION_APPOINT);
                sendPacket(ClientProtocol.U_TONG_BING_CITY_WINNER_APPOINT, respMsg, userId);
                return;
            }
            //任命的时候校验下是不是这个职位已经任命过了
            for(CrossTongBingUserData userDd : new ArrayList<>(userDataMap.values())){
                if(userDd.getPositionId() == reqMsg.getPosition()){
                    respMsg.setRet(GameErrorCode.E_TONG_BING_USER_POSITION_APPOINT_ERR);
                    sendPacket(ClientProtocol.U_TONG_BING_CITY_WINNER_APPOINT, respMsg, userId);
                    return;
                }
            }
            //自己阵营只能buff，别人阵营的只能debuff
            if (winnerUserData.getCampId() == userData.getCampId()) {
                if (titleConfig.getType() != 1) {
                    respMsg.setRet(GameErrorCode.E_TONG_BING_USER_APPOINT_ERR_POSITION);
                    sendPacket(ClientProtocol.U_TONG_BING_CITY_WINNER_APPOINT, respMsg, userId);
                    return;
                }
            } else {
                if (titleConfig.getType() != 2) {
                    respMsg.setRet(GameErrorCode.E_TONG_BING_USER_APPOINT_ERR_POSITION);
                    sendPacket(ClientProtocol.U_TONG_BING_CITY_WINNER_APPOINT, respMsg, userId);
                    return;
                }
            }
            userData.setPositionId(reqMsg.getPosition());
            //任命开始算钱
            userData.setPosAddSilverTime(System.currentTimeMillis());
            if (titleConfig.getType() == 1) {
                addNeedSyncPosUser(userData);
            }

            // 日志
            CrossAutoLogMgr.add(new LogTongBingAppoint(
                    config.getActivityInfo().getActivityId(),
                    userId,
                    reqMsg.getUserId(),
                    titleConfig.getId(),
                    groupId
            ));

            //减益buff还要去本服通知扣除他英两的增加
            UserBaseInfo userBaseInfo = CrossUserMgr.getUserBaseInfo(userData.getUserId());
            UserBaseInfo winnerUserBaseInfo = CrossUserMgr.getUserBaseInfo(winnerUserId);
            if (userBaseInfo != null) {
                TongBingProto.CrossTongBingCityWinerAppointRespMsg.Builder crespMsg = TongBingProto.CrossTongBingCityWinerAppointRespMsg.newBuilder();
                crespMsg.setWinerName(winnerUserBaseInfo == null ? "" : winnerUserBaseInfo.getNickName());
                crespMsg.setPosition(reqMsg.getPosition());
                crespMsg.setSendReward(true);
                MessageHelper.sendPacket(userBaseInfo.getServerId(), userData.getUserId(), YanQuMessageUtils.buildMessage(GameProtocol.S_CROSS_TONG_BING_CITY_APPOINT_POSITION, crespMsg));
            } else {
                logger.error("统兵任命找不到任命玩家的userBaseInfo");
            }
            sendPacket(ClientProtocol.U_TONG_BING_CITY_WINNER_APPOINT, respMsg, userId);
        }
    }

    /**
     * 任命指挥官
     */
    public void campCommanderAppoint(long userId, TongBingProto.CrossTongBingCampCommanderAppointReqMsg reqMsg) {
        TongBingProto.TongBingCampCommanderAppointRespMsg.Builder respMsg = TongBingProto.TongBingCampCommanderAppointRespMsg.newBuilder();
        respMsg.setRet(0);
        CrossTongBingUserData userData = getUserData(userId);
        long maxZhanGongUserId = getCampMaxZhanGongUserId(userData.getCampId());
        if (userId != maxZhanGongUserId || userData.getZhanGong() < config.getAppointScoreLimit()) {
            respMsg.setRet(GameErrorCode.E_TONG_BING_CAN_NOT_SET_COMMANDER);
            sendPacket(ClientProtocol.U_TONG_BING_CAMP_COMMANDER, respMsg, userId);
            return;
        }
        CrossTongBingUserData appointUserData = getUserData(reqMsg.getUserId());//任命的玩家
        synchronized (lock) {
            // 只能任命当前阵营的人
            if (appointUserData.getCampId() != userData.getCampId()) {
                respMsg.setRet(GameErrorCode.E_TONG_BING_COMMANDER_ONLY_SET_THE_SAME_CAMP);
                sendPacket(ClientProtocol.U_TONG_BING_CAMP_COMMANDER, respMsg, userId);
                return;
            }
            CrossTongBingCampData campData = campDataMap.get(userData.getCampId());
            CrossTongBingCommanderData commanderData = campData.getCommanderData();
            if (commanderData == null) {
                commanderData = new CrossTongBingCommanderData();
            }
            commanderData.setUserId(appointUserData.getUserId());
            campData.setCommanderData(commanderData);
            sendPacket(ClientProtocol.U_TONG_BING_CAMP_COMMANDER, respMsg, userId);
            // 同步信息给阵营玩家
            syncCommanderInfoToCamp(userData.getCampId());
        }
    }

    /**
     * 指挥官设置信息
     */
    public void commanderSet(long userId, TongBingProto.CrossTongBingCommanderSetReqMsg reqMsg) {
        TongBingProto.TongBingCommanderSetRespMsg.Builder respMsg = TongBingProto.TongBingCommanderSetRespMsg.newBuilder();
        respMsg.setRet(0);
        CrossTongBingUserData userData = getUserData(userId);
        // 判断是否为指挥官
        synchronized (lock) {
            CrossTongBingCampData campData = campDataMap.get(userData.getCampId());
            CrossTongBingCommanderData commanderData = campData.getCommanderData();
            if (commanderData == null) {
                commanderData = new CrossTongBingCommanderData();
            }
            long maxZhanGongUserId = getCampMaxZhanGongUserId(userData.getCampId());
            // 只有战功第一的或者指挥官才可设置
            boolean isMaxZhangong = maxZhanGongUserId == userId && userData.getZhanGong() >= config.getAppointScoreLimit();
            boolean isCommander = commanderData.getUserId() == userId;
            if (!isMaxZhangong && !isCommander) {
                respMsg.setRet(GameErrorCode.E_TONG_BING_NOT_COMMANDER);
                sendPacket(ClientProtocol.U_TONG_BING_COMMANDER_SET, respMsg, userId);
                return;
            }
            // 设置信息
            boolean isNeedSync = false;
            TongBingProto.TongBingCommanderSetReqMsg commanderSetReqMsg = reqMsg.getCommanderSetReqMsg();
            if (commanderSetReqMsg.hasNotice()) {
                commanderData.setNotice(commanderSetReqMsg.getNotice());
                isNeedSync = true;
            }
            if (commanderSetReqMsg.hasMajorCityId()) {
                commanderData.setMajorCityId(commanderSetReqMsg.getMajorCityId());
                isNeedSync = true;
            }
            if (commanderSetReqMsg.hasMinorCityId()) {
                commanderData.setMinorCityId(commanderSetReqMsg.getMinorCityId());
                isNeedSync = true;
            }
            sendPacket(ClientProtocol.U_TONG_BING_COMMANDER_SET, respMsg, userId);
            if (isNeedSync) {
                campData.setCommanderData(commanderData);
                // 同步信息给阵营玩家
                syncCommanderInfoToCamp(userData.getCampId());
            }
        }
    }

    /**
     * 获取同阵营的用户数据
     */
    public void getCampUserData(long userId, TongBingProto.CrossTongBingCampUserDataReqMsg reqMsg) {
        TongBingProto.TongBingCampUserDataRespMsg.Builder respMsg = TongBingProto.TongBingCampUserDataRespMsg.newBuilder();
        respMsg.setRet(0);
        CrossTongBingUserData userData = getUserData(userId);
        if (!isAble(userData)) {
            respMsg.setRet(GameErrorCode.E_TONG_BING_NO_ABLE);
            sendPacket(ClientProtocol.U_TONG_BING_CAMP_USER_DATA, respMsg, userId);
            return;
        }
        long maxZhanGongUserId = getCampMaxZhanGongUserId(userData.getCampId());
        for (CrossTongBingUserData tongBingUserData : userDataMap.values()) {
            // 获取阵营
            if (tongBingUserData.getCampId() == userData.getCampId()) {
                UserBaseInfo userBaseInfo = CrossUserMgr.getUserBaseInfo(tongBingUserData.getUserId());
                if (userBaseInfo != null) {
                    TongBingProto.TongBingCampUserDataTemp.Builder temp = TongBingProto.TongBingCampUserDataTemp.newBuilder();
                    temp.setUserId(tongBingUserData.getUserId());
                    temp.setPlayerBaseData(PlayerBasePb.parsePlayerBaseTempMsg(userBaseInfo));
                    temp.setZhangong(tongBingUserData.getZhanGong());
                    temp.setIsMaxZhanGong(tongBingUserData.getUserId() == maxZhanGongUserId);
                    respMsg.addCampUserData(temp);
                }
            }
        }
        sendPacket(ClientProtocol.U_TONG_BING_CAMP_USER_DATA, respMsg, userId);
    }


    /**
     * 添加伤兵
     */
    public void addBarracksWoundedSoldier(long userId, long num) {
        barracksCalc(userId);
        CrossTongBingUserData userData = getUserData(userId);
        synchronized (lock) {
            CrossTongBingBarracks barracks = userData.getBarracks();

            // 日志用
            String logBeforeBarracks = JSON.toJSONString(barracks);

            long woundedNum = barracks.getWoundedNum();
            long woundedMaxNum = barracks.getWoundedMaxNum();
            if (woundedNum >= woundedMaxNum) {
                // 到达上限，不操作
            } else if (woundedNum + num >= woundedMaxNum) {
                // 加完后超过上限，设置为上限
                barracks.setWoundedNum(woundedMaxNum);
            } else {
                // 正常添加
                barracks.setWoundedNum(woundedNum + num);
            }
            barracks.setRescueSpeed(config.getWoundedRescueTime());
            barracks.setRescueCount(getBarracksWoundedCount(userId));
            if (barracks.getRescueTime() == 0) {
                barracks.setRescueTime(System.currentTimeMillis());
            }
            userData.setUpdateOption();

            // 日志
            CrossAutoLogMgr.add(new LogTongBingSoldierChange(
                    config.getActivityInfo().getActivityId(),
                    userId,
                    5,
                    num,
                    -1,
                    logBeforeBarracks,
                    JSON.toJSONString(barracks),
                    groupId
            ));
        }
    }

    public void getUserBarracksInfo(long userId) {
        barracksCalc(userId);
        TongBingProto.TongBingGetBarracksRespMsg.Builder respMsg = TongBingProto.TongBingGetBarracksRespMsg.newBuilder();
        CrossTongBingUserData userData = getUserData(userId);
        if (!isAble(userData)) {
            respMsg.setRet(GameErrorCode.E_TONG_BING_NO_ABLE);
            sendPacket(ClientProtocol.U_TONG_BING_GET_BARRACKS, respMsg, userId);
            return;
        }
        respMsg.setRet(0);
        respMsg.setBarracks(parseTongBingBarracksMsg(userId));
        sendPacket(ClientProtocol.U_TONG_BING_GET_BARRACKS, respMsg, userId);
    }

    /**
     * 计算兵营的产出
     */
    private void barracksCalc(long userId) {
        long nowTime = System.currentTimeMillis();
        CrossTongBingUserData userData = getUserData(userId);
        CrossTongBingBarracks barracks = userData.getBarracks();

        synchronized (lock) {
            if (barracks.getCreateNum() > 0) {
                long createTime = barracks.getCreateTime();
                // 征兵了几秒
                long second = (nowTime - createTime) / DateHelper.SECOND_MILLIONS;
                // 征兵了几个
                long createNumber = second * barracks.getCreateSpeed();
                // 可以全部征完
                if (createNumber >= barracks.getCreateNum()) {
                    createNumber = barracks.getCreateNum();
                }

                barracks.setCreateNum(barracks.getCreateNum() - createNumber);
                if (barracks.getCreateNum() == 0) {
                    barracks.setCreateTime(0);
                    //全部征兵完了设置一下速度和花费和数量为0
                    barracks.setCreateSpeed(0);
                    barracks.setCreateCost(0);
                    barracks.setCreateCount(0);
                } else {
                    barracks.setCreateTime(barracks.getCreateTime() + second * DateHelper.SECOND_MILLIONS);
                }
                barracks.setReadyNum(barracks.getReadyNum() + createNumber);
                userData.setUpdateOption();
            }

            if (barracks.getWoundedNum() > 0) {
                long rescueTime = barracks.getRescueTime();
                // 修复次数
                long rescueNumber = (nowTime - rescueTime) / barracks.getRescueSpeed() * barracks.getRescueCount();
                // 时间到了全部伤兵时间
                if (rescueNumber >= barracks.getWoundedNum()) {
                    rescueNumber = barracks.getWoundedNum();
                }
                barracks.setWoundedNum(barracks.getWoundedNum() - rescueNumber);
                if (barracks.getWoundedNum() == 0) {
                    barracks.setRescueTime(0);
                } else {
                    barracks.setRescueTime(barracks.getRescueTime() + (rescueNumber / barracks.getRescueCount()) * barracks.getRescueSpeed());
                }
                barracks.setRescueNum(barracks.getRescueNum() + rescueNumber);
                userData.setUpdateOption();
            }
        }
    }

    /**
     * 兵营玩家道具操作
     *
     * @param type 1扣消耗，2返回预扣除，3不操作，4加道具
     */
    private void sendBarracksOpMsg(long serverId, long userId, int type, String cost, TongBingProto.TongBingRecruitSoldierRespMsg respMsg) {
        TongBingProto.CrossTongBingBarracksOpMsg.Builder builder = TongBingProto.CrossTongBingBarracksOpMsg.newBuilder();
        builder.setActivityId(config.getActivityInfo().getActivityId());
        builder.setType(type);
        builder.setCost(cost);
        if (respMsg != null) {
            builder.setResMsg(respMsg);
        }
        MessageHelper.sendPacket(serverId, userId, YanQuMessageUtils.buildMessage(GameProtocol.S_CROSS_TONG_BING_BARRACKS_OP, builder));
    }

    /**
     * 获取招募所需银两
     */
    public void recruitSoldierSliver(long serverId, long userId, TongBingProto.CrossTongBingRecruitSoldierReqMsg reqMsg) {
        long speed = reqMsg.getSpeed();
        long num = reqMsg.getNum();

        Map<Long, Long> recruitSoldierMap = config.getRecruitSoldierMap();
        CrossTongBingUserData userData = getUserData(userId);
        CrossTongBingBarracks barracks = userData.getBarracks();

        TongBingProto.TongBingRecruitSoldierRespMsg.Builder respMsg = TongBingProto.TongBingRecruitSoldierRespMsg.newBuilder();

        // 没有找到对应速度的招募
        if (!recruitSoldierMap.containsKey(speed)) {
            respMsg.setRet(GameErrorCode.E_TONG_BING_BARRACKS_SPEED);
            sendPacket(ClientProtocol.U_TONG_BING_RECRUIT_SOLDIER, respMsg, userId);
            return;
        }

        // 上一次的招募还没有结束
        barracksCalc(userId);
        if (barracks.getCreateNum() != 0) {
            respMsg.setRet(GameErrorCode.E_TONG_BING_BARRACKS_ING);
            sendPacket(ClientProtocol.U_TONG_BING_RECRUIT_SOLDIER, respMsg, userId);
            return;
        }

        // 超出招募上限
        if (num + barracks.getReadyNum() > barracks.getReadyMaxNum()) {
            respMsg.setRet(GameErrorCode.E_TONG_BING_BARRACKS_FULL);
            sendPacket(ClientProtocol.U_TONG_BING_RECRUIT_SOLDIER, respMsg, userId);
            return;
        }

        // 每秒银两
        BigInteger silver = calcRecruitPerSecondSilver(userId, speed);

        // 每个兵多少钱
        BigInteger silverPerSoldier = new BigDecimal(silver).divide(BigDecimal.valueOf(speed), 0, BigDecimal.ROUND_UP).toBigInteger();

        // 总银两  = 士兵数 * 总银两
        BigInteger totalSilver = silverPerSoldier.multiply(BigInteger.valueOf(num));

        TongBingProto.CrossTongBingRecruitSoldierReqMsg.Builder respServerMsg = reqMsg.toBuilder();
        respServerMsg.setSilver(totalSilver.toString());
        MessageHelper.sendPacket(serverId, userId, YanQuMessageUtils.buildMessage(GameProtocol.S_CROSS_TONG_BING_CALL_SOLDIER_SLIVER_RESP, respServerMsg));
    }

    /**
     * 计算招募每秒消耗银两（含BUFF）
     */
    private BigInteger calcRecruitPerSecondSilver(long userId, long speed) {
        // 拿配置
        Map<Long, Long> recruitSoldierMap = config.getRecruitSoldierMap();
        long silver = recruitSoldierMap.get(speed);
        // 加buff
        Integer buffAddition = getBuffAddition(userId, eTongBingBuffType.Type2_RecruitSoldierPrice).get(0);
        // 计算结果
        BigInteger result = BigDecimal.valueOf(silver)
                .multiply(BigDecimal.valueOf(1000 - buffAddition))
                .divide(BigDecimal.valueOf(1000), 0, BigDecimal.ROUND_UP).toBigInteger();
        // 容错
        if (result.compareTo(BigInteger.ONE) <= 0) {
            result = BigInteger.ONE;
        }
        return result;
    }

    /**
     * 招募士兵
     */
    public void recruitSoldier(long serverId, long userId, TongBingProto.CrossTongBingRecruitSoldierReqMsg reqMsg) {

        barracksCalc(userId);
        CrossTongBingUserData userData = getUserData(userId);
        CrossTongBingBarracks barracks = userData.getBarracks();

        TongBingProto.TongBingRecruitSoldierRespMsg.Builder respMsg = TongBingProto.TongBingRecruitSoldierRespMsg.newBuilder();
        respMsg.setRet(0);

        int type = reqMsg.getType();
        long speed = reqMsg.getSpeed();
        long num = reqMsg.getNum(); // 士兵数量 or 兵符数量

        String costStr = "";

        synchronized (lock) {

            // 日志用
            String logBeforeBarracks = JSON.toJSONString(barracks);

            // 【取消银两招兵】
            if (type == 1 && speed == 0) {
                if (barracks.getCreateNum() <= 0) {
                    respMsg.setRet(GameErrorCode.E_TONG_BING_BARRACKS_NO_NEED_CANCEL);
                    sendBarracksOpMsg(serverId, userId, 3, costStr, respMsg.build());
                    return;
                }

                // 剩余银两 = 每秒银两 x ( 剩余士兵数 / 每秒士兵数 )
                long sliver = barracks.getCreateCost();
                BigInteger cost = BigDecimal.valueOf(sliver).multiply(
                        BigDecimal.valueOf(barracks.getCreateNum()).divide(BigDecimal.valueOf(barracks.getCreateSpeed()), 8, BigDecimal.ROUND_DOWN)
                ).setScale(0, BigDecimal.ROUND_DOWN).toBigInteger();

                // 设置返还银两
                costStr = GameConfig.GAME_MONEY_SILVER + "=" + cost.toString();

                // 修改兵营
                barracks.setCreateCost(0);
                barracks.setCreateCount(0);
                barracks.setCreateSpeed(0);
                barracks.setCreateTime(0);
                barracks.setCreateNum(0);
                userData.setUpdateOption();

                // 返回
                respMsg.setBarracks(parseTongBingBarracksMsg(userId));
                sendBarracksOpMsg(serverId, userId, 4, costStr, respMsg.build());

                // 日志
                CrossAutoLogMgr.add(new LogTongBingSoldierChange(
                        config.getActivityInfo().getActivityId(),
                        userId,
                        4,
                        num,
                        -1,
                        logBeforeBarracks,
                        JSON.toJSONString(barracks),
                        groupId
                ));
                return;
            }


            if (type == 1 && speed != 0) {
                // 【银两募兵】

                // 没有找到对应速度的招募
                Map<Long, Long> recruitSoldierMap = config.getRecruitSoldierMap();
                if (!recruitSoldierMap.containsKey(speed)) {
                    respMsg.setRet(GameErrorCode.E_TONG_BING_BARRACKS_SPEED);
                    sendBarracksOpMsg(serverId, userId, 2, costStr, respMsg.build());
                    return;
                }

                // 上一次的招募还没有结束
                if (barracks.getCreateNum() != 0) {
                    respMsg.setRet(GameErrorCode.E_TONG_BING_BARRACKS_ING);
                    sendBarracksOpMsg(serverId, userId, 2, costStr, respMsg.build());
                    return;
                }

                // 超上限
                if (num + barracks.getReadyNum() > barracks.getReadyMaxNum()) {
                    respMsg.setRet(GameErrorCode.E_TONG_BING_BARRACKS_FULL);
                    sendBarracksOpMsg(serverId, userId, 2, costStr, respMsg.build());
                    return;
                }

                // 设置消耗
                costStr = GameConfig.GAME_MONEY_SILVER + "=" + reqMsg.getSilver();

                // 每个兵的消耗
                BigInteger soldierSilver = new BigDecimal(reqMsg.getSilver()).divide(BigDecimal.valueOf(num), 0, BigDecimal.ROUND_UP).toBigInteger();
                // 每秒消耗
                BigInteger secondSilver = soldierSilver.multiply(BigInteger.valueOf(speed));

                // 改数据
                barracks.setCreateNum(num);
                barracks.setCreateTime(System.currentTimeMillis());
                barracks.setCreateSpeed(speed);
                barracks.setCreateCost(secondSilver.longValue());
                barracks.setCreateCount(1);
                userData.setUpdateOption();

            } else if (type == 2) {
                // 【伤兵救治】
                if (barracks.getRescueNum() < num) {
                    respMsg.setRet(GameErrorCode.E_TONG_BING_BARRACKS_HOSPITAL_NO_ENOUGH);
                    sendBarracksOpMsg(serverId, userId, 3, costStr, respMsg.build());
                    return;
                }
                barracks.setReadyNum(barracks.getReadyNum() + num);
                barracks.setRescueNum(barracks.getRescueNum() - num);
                userData.setUpdateOption();

            } else if (type == 3) {
                // 【兵符募兵】

                // 设置消耗
                costStr = config.getBingFuItem() + "=" + num;

                barracks.setReadyNum(barracks.getReadyNum() + num * reqMsg.getBingFuNum());
                userData.setUpdateOption();

            } else {
                // 【参数错误】

                respMsg.setRet(GameErrorCode.E_TONG_BING_PARAM_ERR);
                sendBarracksOpMsg(serverId, userId, 2, costStr, respMsg.build());
                return;
            } // 【加类型下面日志要改】

            respMsg.setBarracks(parseTongBingBarracksMsg(userId));
            sendBarracksOpMsg(serverId, userId, 1, costStr, respMsg.build());

            // 日志
            CrossAutoLogMgr.add(new LogTongBingSoldierChange(
                    config.getActivityInfo().getActivityId(),
                    userId,
                    type, // 123刚好是对应的
                    num,
                    type == 1 ? speed : -1,
                    logBeforeBarracks,
                    JSON.toJSONString(barracks),
                    groupId
            ));
        }
    }

    public void changeSoldier(long userId, TongBingProto.CrossTongBingChangeTeamSoldierReqMsg reqMsg) {
        TongBingProto.TongBingChangeTeamSoldierRespMsg.Builder respMsg = TongBingProto.TongBingChangeTeamSoldierRespMsg.newBuilder();
        respMsg.setRet(0);
        CrossTongBingUserData userData = getUserData(userId);
        if (!isAble(userData)) {
            respMsg.setRet(GameErrorCode.E_TONG_BING_NO_ABLE);
            sendPacket(ClientProtocol.U_TONG_BING_CHANGE_TEAM_SOLDIER, respMsg, userId);
            return;
        }

        barracksCalc(userId);

        Map<Integer, CrossTongBingTeam> teamMap = userData.getTeamMap();
        long allValue = 0L;
        synchronized (lock) {

            // 日志用
            String logBeforeBarracks = JSON.toJSONString(userData.getBarracks());

            for (TongBingProto.TongBingChangeTeamSoldierMsg changeMsg : reqMsg.getChangeList()) {
                int occType = changeMsg.getOccupation();
                CrossTongBingTeam team = teamMap.get(occType);
                if (getTeamStatus(team) != eTongBingTeamStatus.Free) {
                    respMsg.setRet(GameErrorCode.E_TONG_BING_TEAM_CHANGE_SOLDIER_NO_FREE);
                    sendPacket(ClientProtocol.U_TONG_BING_CHANGE_TEAM_SOLDIER, respMsg, userId);
                    return;
                }
                if (changeMsg.getSoldierNum() > team.getMaxSoldier() || changeMsg.getSoldierNum() < 0) {
                    respMsg.setRet(GameErrorCode.E_TONG_BING_TEAM_CHANGE_SOLDIER_MAX);
                    sendPacket(ClientProtocol.U_TONG_BING_CHANGE_TEAM_SOLDIER, respMsg, userId);
                    return;
                }
                allValue = allValue + (changeMsg.getSoldierNum() - team.getSoldierNum());
            }
            if (allValue > userData.getBarracks().getReadyNum()) {
                respMsg.setRet(GameErrorCode.E_TONG_BING_TEAM_CHANGE_SOLDIER_NO_ENOUGH);
                sendPacket(ClientProtocol.U_TONG_BING_CHANGE_TEAM_SOLDIER, respMsg, userId);
                return;
            }
            for (TongBingProto.TongBingChangeTeamSoldierMsg changeMsg : reqMsg.getChangeList()) {
                int occType = changeMsg.getOccupation();
                CrossTongBingTeam team = teamMap.get(occType);
                team.setSoldierNum(changeMsg.getSoldierNum());
            }
            userData.setUpdateOption();

            for (CrossTongBingTeam team : teamMap.values()) {
                respMsg.addTeam(parseTongBingTeamMsg(userData, team));
            }
            CrossTongBingBarracks barracks = userData.getBarracks();
            barracks.setReadyNum(userData.getBarracks().getReadyNum() - allValue);
            userData.setUpdateOption();
            respMsg.setBarracks(parseTongBingBarracksMsg(userId));
            sendPacket(ClientProtocol.U_TONG_BING_CHANGE_TEAM_SOLDIER, respMsg, userId);

            // 日志
            CrossAutoLogMgr.add(new LogTongBingSoldierChange(
                    config.getActivityInfo().getActivityId(),
                    userId,
                    6,
                    allValue,
                    -1,
                    logBeforeBarracks,
                    JSON.toJSONString(barracks),
                    groupId
            ));
        }
    }

    public void makeNpcEvent() {
        long npcEventAddTime = getCrossTongBingData().getNpcEventAddTime();
        long nowTime = System.currentTimeMillis();
        long startTime = DateHelper.getTodayZeroTimeStamp() + config.getNpcEventBeginTime();

        // 今天未开始 || 小于上次生成时间 || 活动结束了
        if (nowTime < startTime || nowTime < npcEventAddTime || nowTime > config.getActivityInfo().getEndTime() * 1000L) {
            return;
        }

        for (CrossTongBingUserData userData : userDataMap.values()) {
            if (userData.getEventResetTime() != DateHelper.getTodayZeroTimeStamp() + config.getNpcEventBeginTime()) {
                userData.setEventResetTime(DateHelper.getTodayZeroTimeStamp() + config.getNpcEventBeginTime());
                userData.setEventTodayCount(0);
            }
        }

        Map<Long, Map<Long, Map<Integer, Integer>>> serverSyncMap = new ConcurrentHashMap<>();
        if (nowTime >= npcEventAddTime + config.getNpcEventInterval()) {
            long times = (nowTime - npcEventAddTime) / config.getNpcEventInterval();
            npcEventAddTime = npcEventAddTime + times * config.getNpcEventInterval();
            for (int i = 0; i < times; i++) {
                for (CrossTongBingUserData userData : userDataMap.values()) {
                    Map<Integer, Integer> userEventMap = userData.getEventMap();
                    if (userEventMap.size() < config.getNpcEventMaxNum() && userData.getEventTodayCount() < config.getNpcEventDayMaxNum()) {
                        int eventPos = randomNpcEventPosition(userEventMap);
                        if (eventPos != -1) {
                            TongBingNpcEventConfig npcEventConfig = randomNpcEvent();
                            userEventMap.put(eventPos, npcEventConfig.getId());
                            userData.setUpdateOption();
                            userData.setEventTodayCount(userData.getEventTodayCount() + 1);
                            UserBaseInfo userBaseInfo = CrossUserMgr.getUserBaseInfo(userData.getUserId());
                            if (userBaseInfo != null) {
                                if (!serverSyncMap.containsKey(userBaseInfo.getServerId())) {
                                    serverSyncMap.put(userBaseInfo.getServerId(), new ConcurrentHashMap<>());
                                }
                                Map<Long, Map<Integer, Integer>> userSyncMap = serverSyncMap.get(userBaseInfo.getServerId());
                                if (!userSyncMap.containsKey(userData.getUserId())) {
                                    userSyncMap.put(userData.getUserId(), new ConcurrentHashMap<>());
                                }
                                userSyncMap.get(userData.getUserId()).put(eventPos, npcEventConfig.getId());
                            }
                            // 加红点
                            addRedDot(userData, eTongBingRedDotType.Type1_NpcEvent);
                            // 日志
                            CrossAutoLogMgr.add(new LogTongBingNpcEvent(
                                    config.getActivityInfo().getActivityId(),
                                    userData.getUserId(),
                                    1,
                                    npcEventConfig.getId(),
                                    -1,
                                    "-1",
                                    groupId
                            ));
                        }
                    }
                }
            }
        }
        if (serverSyncMap.size() > 0) {
            syncServerNpcEvent(serverSyncMap);
        }
        getCrossTongBingData().setNpcEventAddTime(npcEventAddTime);
    }

    //随机npc事件位置
    private int randomNpcEventPosition(Map<Integer, Integer> userEventMap) {
        List<Integer> eventPosList = new ArrayList<>();
        int npcEventPositionNum = config.getNpcEventPositionNum();
        if (userEventMap.size() >= npcEventPositionNum) {
            return -1;
        }
        //十个位置就是0到9
        for (int i = 0; i < npcEventPositionNum; i++) {
            if (!userEventMap.containsKey(i)) {
                eventPosList.add(i);
            }
        }
        int randomValue = randomHelper.next(0, eventPosList.size());
        return eventPosList.get(randomValue);
    }

    private TongBingNpcEventConfig randomNpcEvent() {
        Map<Integer, TongBingNpcEventConfig> eventConfigMap = config.getNpcEventConfigMap();
        TongBingNpcEventConfig randomNpcEventConfig = null;
        int totalWeight = 0;
        for (TongBingNpcEventConfig npcEventConfig : eventConfigMap.values()) {
            int weight = npcEventConfig.getWeight();
            totalWeight += weight;
        }
        int randomNum = randomHelper.next(0, totalWeight);
        for (TongBingNpcEventConfig npcEventConfig : eventConfigMap.values()) {
            int value = npcEventConfig.getWeight();
            if (randomNum < value) {
                randomNpcEventConfig = npcEventConfig;
                break;
            }
            randomNum -= value;
        }
        return randomNpcEventConfig;
    }

    //处理事件
    public void dealEvent(long userId, TongBingProto.CrossTongBingDealEventReqMsg reqMsg) {
        TongBingProto.TongBingDealEventRespMsg.Builder respMsg = TongBingProto.TongBingDealEventRespMsg.newBuilder();
        respMsg.setRet(0);
        CrossTongBingUserData userData = getUserData(userId);
        if (!isAble(userData)) {
            respMsg.setRet(GameErrorCode.E_TONG_BING_NO_ABLE);
            sendPacket(ClientProtocol.U_TONG_BING_DEAL_EVENT, respMsg, userId);
            return;
        }
        synchronized (lock) {
            Map<Integer, Integer> eventMap = userData.getEventMap();
            if (!eventMap.containsKey(reqMsg.getPositionNo())) {
                respMsg.setRet(GameErrorCode.E_TONG_BING_EVENT_POSITION);
                sendPacket(ClientProtocol.U_TONG_BING_DEAL_EVENT, respMsg, userId);
                return;
            }
            int eventId = eventMap.get(reqMsg.getPositionNo());
            Map<Integer, TongBingNpcEventConfig> eventConfigMap = config.getNpcEventConfigMap();
            TongBingNpcEventConfig eventConfig = eventConfigMap.get(eventId);
            List<String> rewardList = eventConfig.getRewardList();
            String reward = "";
            int randomValue = -1;
            if (eventConfig.getType() == eTongBingNpcEventType.Option.getValue()) {
                if (reqMsg.getChoice() > rewardList.size() || reqMsg.getChoice() < 1) {
                    respMsg.setRet(GameErrorCode.E_TONG_BING_EVENT_CHOICE);
                    sendPacket(ClientProtocol.U_TONG_BING_DEAL_EVENT, respMsg, userId);
                    return;
                }
                reward = rewardList.get(reqMsg.getChoice() - 1);
            } else if (eventConfig.getType() == eTongBingNpcEventType.Dispatch.getValue()) {
                randomValue = randomHelper.next(0, rewardList.size());
                reward = rewardList.get(randomValue);
            } else if (eventConfig.getType() == eTongBingNpcEventType.Event.getValue()) {
                randomValue = randomHelper.next(0, rewardList.size());
                reward = rewardList.get(randomValue);
            }
            respMsg.setRandom(randomValue);
            respMsg.setReward(reward);
            eventMap.remove(reqMsg.getPositionNo());
            userData.setUpdateOption();
            sendPacket(ClientProtocol.U_TONG_BING_DEAL_EVENT, respMsg, userId);

            // 发游戏服加奖励
            TongBingProto.CrossTongBingAddRewardReqMsg.Builder builder = TongBingProto.CrossTongBingAddRewardReqMsg.newBuilder();
            builder.setReward(reward);
            builder.setLogType1(eLogMoneyType.TongBing.getValue());
            builder.setLogType2(eLogMoneyType.TongBingNpcEventReward.getValue());
            sendPacket(GameProtocol.S_TONG_BING_ADD_REWARD, builder, userId);

            // 日志
            CrossAutoLogMgr.add(new LogTongBingNpcEvent(
                    config.getActivityInfo().getActivityId(),
                    userId,
                    2,
                    eventId,
                    eventConfig.getType() == eTongBingNpcEventType.Option.getValue() ? reqMsg.getChoice() : -1,
                    reward,
                    groupId
            ));
        }
    }

    public void getBattleFieldByCmd(long userId, TongBingProto.TongBingGetBattlefieldReqMsg reqMsg) {
        int ret = getBattleField(userId, reqMsg);
        if (ret != 0) {
            TongBingProto.TongBingGetBattlefieldRespMsg.Builder builder = TongBingProto.TongBingGetBattlefieldRespMsg.newBuilder();
            builder.setRet(ret);
            sendPacket(ClientProtocol.U_TONG_BING_GET_BATTLE_FIELD, builder, userId);
        }
    }

    public void getCityDetailByCmd(long userId, TongBingProto.TongBingGetCityDetailReqMsg reqMsg) {
        int cityId = reqMsg.getCityId();
        CrossTongBingCity city = getCrossTongBingData().getCityMap().get(cityId);

        if (city == null) {
            TongBingProto.TongBingGetCityDetailRespMsg.Builder builder = TongBingProto.TongBingGetCityDetailRespMsg.newBuilder();
            builder.setRet(GameErrorCode.E_TONG_BING_PARAM_ERR);
            sendPacket(ClientProtocol.U_TONG_BING_GET_CITY_DETAIL, builder, userId);
            return;
        }

        TongBingProto.TongBingGetCityDetailRespMsg.Builder builder = TongBingProto.TongBingGetCityDetailRespMsg.newBuilder();
        builder.setRet(0);
        builder.setCity(parseTongBingCityDetailMsg(city));
        sendPacket(ClientProtocol.U_TONG_BING_GET_CITY_DETAIL, builder, userId);
    }

    public void getCityTeamByCmd(long userId, TongBingProto.TongBingSyncCityTeamReqMsg reqMsg) {
        getCityTeam(userId, reqMsg);
    }

    public void useWarDrumByCmd(long userId, TongBingProto.CrossTongBingUseWarDrumReqMsg reqMsg) {
        int ret = useWarDrum(userId, reqMsg.getReq());

        TongBingProto.TongBingUseWarDrumRespMsg.Builder resp = TongBingProto.TongBingUseWarDrumRespMsg.newBuilder();
        resp.setRet(ret);

        TongBingProto.CrossTongBingUseWarDrumRespMsg.Builder builder = TongBingProto.CrossTongBingUseWarDrumRespMsg.newBuilder();
        builder.setResp(resp);
        builder.setCost(reqMsg.getCost());
        sendPacket(GameProtocol.S_TONG_BING_USE_WAR_DRUM_FROM_CROSS, builder, userId);
    }

    public void needHelpByCmd(long userId, TongBingProto.TongBingNeedHelpReqMsg reqMsg) {
        int ret = needHelp(userId, reqMsg);

        TongBingProto.TongBingNeedHelpRespMsg.Builder builder = TongBingProto.TongBingNeedHelpRespMsg.newBuilder();
        builder.setRet(ret);
        if (ret == 0) {
            builder.setSyncNeedHelpTime(getUserData(userId).getNeedHelpTime());
        }
        sendPacket(ClientProtocol.U_TONG_BING_NEED_HELP, builder, userId);
    }

    public void getBattlefieldRankByCmd(long userId, TongBingProto.TongBingGetBattlefieldRankReqMsg reqMsg) {
        int cityId = reqMsg.getCityId();
        CrossTongBingCity city = getCrossTongBingData().getCityMap().get(cityId);

        TongBingProto.TongBingGetBattlefieldRankRespMsg.Builder builder = TongBingProto.TongBingGetBattlefieldRankRespMsg.newBuilder();
        if (city == null) {
            builder.setRet(GameErrorCode.E_TONG_BING_CITY_NO_EXIST);
            sendPacket(ClientProtocol.U_TONG_BING_GET_BATTLEFIELD_RANK, builder, userId);
            return;
        }
        CrossTongBingBattlefield battlefield = city.getBattlefield();
        if (battlefield == null) {
            builder.setRet(GameErrorCode.E_TONG_BING_NO_BATTLE);
            sendPacket(ClientProtocol.U_TONG_BING_GET_BATTLEFIELD_RANK, builder, userId);
            return;
        }

        builder.setRet(0);

        int rankNum = 0;
        int myRank = -1;
        long myValue = 0;
        for (CrossTongBingBattlefieldRank rank : new LinkedList<>(battlefield.getBattlefieldRankNewList())) {
            rankNum++;
            if (rank.getUserId() == userId) {
                myValue = rank.getZhanGong();
                myRank = rankNum;
            }
            builder.addList(parseTongBingBattlefieldRankMsg(rank));
        }

        builder.setMyRank(myRank);
        builder.setMyValue(myValue);
        sendPacket(ClientProtocol.U_TONG_BING_GET_BATTLEFIELD_RANK, builder, userId);
    }

    public void sendChatByCmd(long userId, CrossActivityProto.CrossActivityChatReqMsg.Builder reqMsg) {
        int range = 0;
        int id = 0;
        if (eMessageType.TongBingPublicChat.getValue() == reqMsg.getMsg().getType()) {
            range = CrossTongBingChatLogic.RANGE_ALL;
            id = config.getActivityInfo().getActivityId();
            // 公屏聊天加上阵营
            WorldMessageProto.WorldMessageTempMsg.Builder message = reqMsg.getMsg().toBuilder();
            message.setExtraParams(String.valueOf(getUserData(userId).getCampId()));
            reqMsg.setMsg(message);
        } else if (eMessageType.TongBingCampChat.getValue() == reqMsg.getMsg().getType()) {
            range = CrossTongBingChatLogic.RANGE_CAMP;
            id = getUserData(userId).getCampId();
        } else if (eMessageType.TongBingBattlefieldChat.getValue() == reqMsg.getMsg().getType()) {
            range = CrossTongBingChatLogic.RANGE_BATTLEFIELD;
            UserScene userScene = CrossSceneMgr.getUserScene(userId);
            if (userScene != null && eSceneType.TongBingBattlefield.getValue() == userScene.getSceneType()) {
                try {
                    String cityIdStr = userScene.getSceneId().split("-")[1];
                    id = Integer.valueOf(cityIdStr);
                } catch (Exception e) {
                }
            }
        }

        if (range <= 0 || id <= 0) {
            return;
        }

        chatLogic.chat(range, id, 1, reqMsg.getMsg());
    }

    public void getChatByCmd(long userId, List<Integer> paramList, int param2) {
        List<WorldMessageProto.WorldMessageTempMsg> dataList = new ArrayList<>();
        if (paramList.isEmpty()) {
            return;
        }
        for (int range : paramList) {
            int id = 0;
            if (range == CrossTongBingChatLogic.RANGE_ALL) {
                id = config.getActivityInfo().getActivityId();
            } else if (range == CrossTongBingChatLogic.RANGE_CAMP) {
                id = getUserData(userId).getCampId();
            } else if (range == CrossTongBingChatLogic.RANGE_BATTLEFIELD) {
                // 城池id
                id = param2;
            }

            if (range <= 0 || id <= 0) {
                continue;
            }
            dataList.addAll(chatLogic.getChatMsg(range, id));
        }

        CrossActivityProto.CrossActivityChatListRespMsg.Builder respMsg = CrossActivityProto.CrossActivityChatListRespMsg.newBuilder();
        respMsg.setActivityId(config.getActivityInfo().getActivityId());
        UserBaseInfo userBaseInfo = CrossUserMgr.getUserBaseInfo(userId);
        if (userBaseInfo != null) {

            if (dataList.size() > 0) {
                dataList.sort(Comparator.comparingLong(WorldMessageProto.WorldMessageTempMsg::getSendTime));
                WorldMessageProto.WorldMessageListMsg.Builder worldMessageListMsg = WorldMessageProto.WorldMessageListMsg.newBuilder();
                worldMessageListMsg.addAllDataList(dataList);
                respMsg.setDataList(worldMessageListMsg);
            }
            YanQuMessage pbMsg = YanQuMessageUtils.buildMessage(Protocol.U_CROSS_ACTIVITY_CHAT_LIST, respMsg);
            MessageHelper.sendPacket(userBaseInfo.getServerId(), userId, pbMsg);
        }
    }

    public void getRankRewardByCmd(long userId, TongBingProto.TongBingGetRankRewardReqMsg reqMsg) {
        int ret = getRankReward(userId, reqMsg.getConditionType());
        if (ret != 0) {
            TongBingProto.TongBingGetRankRewardRespMsg.Builder builder = TongBingProto.TongBingGetRankRewardRespMsg.newBuilder();
            builder.setRet(ret);
            sendPacket(ClientProtocol.U_TONG_BING_GET_RANK_REWARD, builder, userId);
        }
    }

    public void getCampRankListByCmd(long userId) {
        TongBingProto.TongBingGetCampRankRespMsg.Builder builder = TongBingProto.TongBingGetCampRankRespMsg.newBuilder();
        builder.setRet(0);

        for (CrossTongBingRank rank : getCampScoreRankList()) {
            builder.addRank(parseTongBingCampRankMsg(rank));
        }

        sendPacket(ClientProtocol.U_TONG_BING_GET_CAMP_RANK_LIST, builder, userId);
    }

    public void getCampUnionRankListByCmd(long userId, TongBingProto.TongBingGetCampUnionRankReqMsg reqMsg) {
        int campId = reqMsg.getCampId();
        CrossTongBingUserData userData = getUserData(userId);
        String unionUid = userData.getUnionUid();

        TongBingProto.TongBingGetCampUnionRankRespMsg.Builder builder = TongBingProto.TongBingGetCampUnionRankRespMsg.newBuilder();
        builder.setRet(0);
        List<CrossTongBingRank> rankList = new ArrayList<>();
        if (campId == 0) {
            // 获取全部商会
            for (CrossTongBingRankListModel model : campUnionRankMap.values()) {
                if (model != null) {
                    rankList.addAll(model.getRankList());
                }
            }
        } else {
            CrossTongBingRankListModel model = campUnionRankMap.get(campId);
            if (model != null) {
                rankList.addAll(model.getRankList());
            }
        }
        rankList.sort(new CrossTongBingRankSort());

        int rankShowLimit = unionMaxRankConfig;
        if (campId == 0) {
            rankShowLimit = unionMaxRankConfig * config.getCampNum();
        }
        // 获取商会排名
        int num = 0;
        for (CrossTongBingRank rank : rankList) {
            num++;
            if (num > rankShowLimit) {
                break;
            }
            builder.addRank(parseTongBingCampUnionRankMsg(rank));
        }

        // 获取商会阵营排名
        int myRank = -1;
        int myCampId = userData.getCampId();
        if (myCampId != 0) {
            CrossTongBingRankListModel model = campUnionRankMap.get(myCampId);
            if (model != null) {
                int cnt = 0;
                for (CrossTongBingRank rank : model.getRankList()) {
                    cnt++;
                    if (cnt > rankShowLimit) {
                        break;
                    }
                    if (Objects.equals(rank.getId(), unionUid)) {
                        myRank = cnt;
                    }
                }
            }
        }
        builder.setMyRank(myRank);

        long myValue = 0L;
        CrossTongBingUnionJoin unionJoin = getUnionJoin(unionUid);
        if (unionJoin != null) {
            for (CrossTongBingUnionJoinMember member : unionJoin.getMemberMap().values()) {
                CrossTongBingUserData tmp = getUserData(member.getUserId());
                myValue += tmp.getZhanGong();
            }
        }
        builder.setMyValue(myValue);

        sendPacket(ClientProtocol.U_TONG_BING_GET_CAMP_UNION_RANK_LIST, builder, userId);
    }

    public void getUserRankListByCmd(long userId, TongBingProto.TongBingGetUserRankReqMsg reqMsg) {
        int type = reqMsg.getType();

        long myValue = getUserData(userId).getZhanGong();
        String myId = String.valueOf(userId);
        LinkedList<CrossTongBingRank> rankList;
        int maxRank = 100;

        // 【查其他玩家排名】
        if (type == 3) {
            getOtherUserRankByCmd(userId, reqMsg.getParam());
            return; // ！！！
        }

        if (type == 1) {
            // 【个人榜】
            rankList = userRank.getRankList();
            maxRank = userMaxRankConfig;
        } else if (type == 2) {
            // 【商会内个人榜】
            String unionUid = reqMsg.getParam();
            CrossTongBingUnionJoin unionJoin = getUnionJoin(unionUid);
            if (StringUtils.isNullOrEmpty(unionUid) || unionJoin == null) {
                return; // 参数错误
            }
            // 组装个榜单
            CrossTongBingRankListModel tmpModel = new CrossTongBingRankListModel();
            LinkedList<CrossTongBingRank> tmpList = new LinkedList<>();
            for (Long memberId : unionJoin.getMemberMap().keySet()) {
                CrossTongBingUserData member = getUserData(memberId);
                tmpList.add(new CrossTongBingRank(String.valueOf(member.getUserId()), member.getZhanGong(), 0L));
            }
            tmpModel.setRankList(tmpList); // 排序
            rankList = tmpModel.getRankList();
        } else if (type == 4) {
            // 【商会内个人今天榜】
            String unionUid = reqMsg.getParam();
            CrossTongBingUnionJoin unionJoin = getUnionJoin(unionUid);
            if (StringUtils.isNullOrEmpty(unionUid) || unionJoin == null) {
                return; // 参数错误
            }
            // 组装个榜单
            CrossTongBingRankListModel tmpModel = new CrossTongBingRankListModel();
            LinkedList<CrossTongBingRank> tmpList = new LinkedList<>();
            for (Long memberId : unionJoin.getMemberMap().keySet()) {
                CrossTongBingUserData member = getUserData(memberId);
                tmpList.add(new CrossTongBingRank(String.valueOf(member.getUserId()), member.getZhanGong(), member.getZhanGongTime()));
            }
            tmpModel.setRankList(tmpList); // 排序
            rankList = tmpModel.getRankList();
        } else {
            return; // 参数错误
        }

        TongBingProto.TongBingGetUserRankRespMsg.Builder builder = TongBingProto.TongBingGetUserRankRespMsg.newBuilder();
        builder.setRet(0);

        int myRank = -1;
        int rankNum = 0;
        int beginRank = 0;
        int endRank = 0;
        if (type == 1 && reqMsg.hasPageNo()) {
            beginRank = Math.max(reqMsg.getPageNo() * reqMsg.getPageSize() + 1, 1);
            endRank = Math.min(beginRank + reqMsg.getPageSize() - 1, maxRank);
        }

        for (CrossTongBingRank rank : rankList) {
            rankNum++;
            if (rankNum > maxRank) {
                break;
            }
            if (Objects.equals(rank.getId(), myId)) {
                myRank = rankNum;
            }

            if (type == 1 && reqMsg.hasPageNo()) {
                if (rankNum >= beginRank && rankNum <= endRank) {
                    builder.addRank(parseTongBingUserRankMsg(rank));
                }
            } else {
                builder.addRank(parseTongBingUserRankMsg(rank));
            }
        }

        builder.setMyRank(myRank);
        builder.setMyValue(myValue);
        if (reqMsg.hasPageNo() && reqMsg.hasPageSize()) {
            builder.setPageNo(reqMsg.getPageNo());
            builder.setPageSize(reqMsg.getPageSize());
        }

        sendPacket(ClientProtocol.U_TONG_BING_GET_USER_RANK_LIST, builder, userId);
    }

    public void getOtherUserRankByCmd(long userId, String otherUserId) {
        int otherRank = -1;
        long otherValue = 0L;

        int num = 0;
        for (CrossTongBingRank rank : userRank.getRankList()) {
            num++;
            if (num > userMaxRankConfig) {
                break;
            }
            if (Objects.equals(rank.getId(), otherUserId)) {
                otherRank = num;
                otherValue = rank.getValue();
                break;
            }
        }

        TongBingProto.TongBingGetUserRankRespMsg.Builder builder = TongBingProto.TongBingGetUserRankRespMsg.newBuilder();
        builder.setRet(0);
        builder.setMyRank(otherRank);
        builder.setMyValue(otherValue);
        // builder.setRank(); // 传排名，不需要传rankList
        sendPacket(ClientProtocol.U_TONG_BING_GET_USER_RANK_LIST, builder, userId);
    }

    public void readRedDotByCmd(long userId, TongBingProto.TongBingReadRedDotReqMsg reqMsg) {
        CrossTongBingUserData userData = getUserData(userId);
        if (isAble(userData)) {
            userData.getRedDotMap().remove(reqMsg.getType());
            userData.setUpdateOption();
        }
    }

    // ============================== SYNC ==============================

    public void syncBaseDataMsg(CrossTongBingUserData userData) {
        if (userData == null) {
            return;
        }

        int joinStatus = 0; // 0未报名、1已报名、2有资格、3商会未入选
        if (getCrossTongBingData().isLockUnion()) {
            joinStatus = isAble(userData) ? 2 : 3;
        } else {
            for (CrossTongBingUnionJoin join : unionJoinMap.values()) {
                if (join.getMemberMap().containsKey(userData.getUserId())) {
                    joinStatus = 1;
                    break;
                }
            }
        }

        TongBingProto.TongBingSyncBaseDataRespMsg.Builder builder = TongBingProto.TongBingSyncBaseDataRespMsg.newBuilder();
        builder.setJoinStatus(joinStatus);
        if (userData.getCampId() != 0) {
            builder.setCampId(userData.getCampId());
        }
        builder.setSyncPatronsTime(userData.getLastSyncPatronsTime());
        builder.setGuide(userData.getGuide());
        builder.setSyncNeedHelpTime(userData.getNeedHelpTime());
        sendPacket(ClientProtocol.U_TONG_BING_SYNC_BASE_DATA, builder, userData.getUserId());
    }

    /**
     * 跨服同步用户数据到游戏服
     */
    public void syncUserDataToGameServer(CrossTongBingUserData userData) {
        if (userData == null) {
            return;
        }
        TongBingProto.CrossTongBingSyncUserDataMsg.Builder builder = TongBingProto.CrossTongBingSyncUserDataMsg.newBuilder();
        builder.setCampId(userData.getCampId());
        builder.setZhanGong(userData.getZhanGong());
        sendPacket(GameProtocol.S_CROSS_TONG_BING_SYNC_USER_DATA, builder, userData.getUserId());
    }

    public void syncTeamEnterBattlefield(CrossTongBingCity city, CrossTongBingBattlefield battlefield, CrossTongBingBattlefieldPart part) {

        // 获取场景内玩家
        List<Long> userIds = getBattlefieldUserIdList(city.getCityId(), part.getPartId());

        TongBingProto.TongBingSyncTeamEnterRespMsg.Builder builder = TongBingProto.TongBingSyncTeamEnterRespMsg.newBuilder();
        builder.setBattlefield(parseTongBingBattlefieldMsg(part, battlefield, city));

        sendPacketToAll(ClientProtocol.U_TONG_BING_SYNC_TEAM_ENTER, builder, userIds);
    }

    public void syncUseWarDrum(CrossTongBingCity city, CrossTongBingBattlefield battlefield, CrossTongBingBattlefieldPart part) {

        // 获取场景内玩家
        List<Long> userIds = getBattlefieldUserIdList(city.getCityId(), part.getPartId());

        TongBingProto.TongBingSyncUseDrumRespMsg.Builder builder = TongBingProto.TongBingSyncUseDrumRespMsg.newBuilder();
        builder.setBattlefield(parseTongBingBattlefieldMsg(part, battlefield, city));

        sendPacketToAll(ClientProtocol.U_TONG_BING_SYNC_WAR_DRUM, builder, userIds);
    }

    public void syncBattleResultMsg(List<CrossTongBingBattleResult> resultList, CrossTongBingCityWarReport report) {
        for (CrossTongBingBattleResult result : resultList) {

            // 获取战场内玩家
            List<Long> userIds = getBattlefieldUserIdList(result.getCity().getCityId(), result.getPart().getPartId());
            if (userIds.isEmpty()) {
                continue; // 没人在里面，不同步了
            }

            // 战场数据
            TongBingProto.TongBingBattlefieldMsg.Builder battlefieldMsg = parseTongBingBattlefieldMsg(result.getPart(), result.getBattlefield(), result.getCity());

            // 战斗结果数据
            TongBingProto.TongBingBattleResultMsg.Builder battleResultMsg = TongBingProto.TongBingBattleResultMsg.newBuilder();
            battleResultMsg.setDefWin(result.getWinType() == 1);
            battleResultMsg.setDefHpReduce(result.getDefHpReduce());
            battleResultMsg.setAtkHpReduce(result.getAtkHpReduce());
            battleResultMsg.setBattlefield(battlefieldMsg);
            if (report != null) {
                battleResultMsg.setResult(parseTongBingCityWarReportMsg(report));
            }
            battleResultMsg.setWinTimes(result.getWinTimes());

            // 返回值
            TongBingProto.TongBingSyncBattleResultMsg.Builder builder = TongBingProto.TongBingSyncBattleResultMsg.newBuilder();
            builder.setBattle(battleResultMsg);

            sendPacketToAll(ClientProtocol.U_TONG_BING_BATTLE_RESULT, builder, userIds);
        }
    }

    public void syncBarracksMsg(long userId) {
        CrossTongBingUserData userData = getUserData(userId);
        if (isInActivity(userData, System.currentTimeMillis())) {
            TongBingProto.TongBingSyncBarracksRespMsg.Builder respMsg = TongBingProto.TongBingSyncBarracksRespMsg.newBuilder();
            respMsg.setBarracks(parseTongBingBarracksMsg(userId));
            sendPacket(ClientProtocol.U_TONG_BING_SYNC_BARRACKS, respMsg, userId);
        }
    }

    public void syncCityTeamToSelf(CrossTongBingCity city) {
        // 拿所有队伍
        List<CrossTongBingTeam> teamList = new ArrayList<>();
        for (CrossTongBingTeamArrive teamArrive : new LinkedList<>(city.getAtkTeamList())) {
            teamList.add(getTeamByArrive(teamArrive));
        }
        for (CrossTongBingTeamArrive teamArrive : new LinkedList<>(city.getDefTeamList())) {
            teamList.add(getTeamByArrive(teamArrive));
        }
        CrossTongBingBattlefield battlefield = city.getBattlefield();
        if (battlefield != null) {
            for (CrossTongBingBattlefieldPart part : battlefield.getPartMap().values()) {
                for (CrossTongBingBattlefieldTeam tmp : new LinkedList<>(part.getAtkList())) {
                    teamList.add(getTeamByArrive(tmp.getTeam()));
                }
                for (CrossTongBingBattlefieldTeam tmp : new LinkedList<>(part.getDefList())) {
                    if (tmp.getTeam() != null) {
                        teamList.add(getTeamByArrive(tmp.getTeam()));
                    }
                }
            }
        }

        // 按玩家分类
        Map<Long, List<CrossTongBingTeam>> userTeamMap = new ConcurrentHashMap<>();
        for (CrossTongBingTeam team : teamList) {
            userTeamMap.putIfAbsent(team.getUserId(), new ArrayList<>());
            userTeamMap.get(team.getUserId()).add(team);
        }

        // 按玩家同步
        for (Map.Entry<Long, List<CrossTongBingTeam>> entry : userTeamMap.entrySet()) {
            syncTeamToSelf(entry.getKey(), entry.getValue());
        }
    }

    public void syncTeamToSelf(long userId, List<CrossTongBingTeam> teamList) {
        long now = System.currentTimeMillis();
        if (!isInActivity(getUserData(userId), now)) {
            return;
        }

        TongBingProto.TongBingSyncTeamRespMsg.Builder builder = TongBingProto.TongBingSyncTeamRespMsg.newBuilder();
        for (CrossTongBingTeam team : teamList) {
            builder.addTeam(parseTongBingTeamMsg(getUserData(team.getUserId()), team));
        }
        sendPacket(ClientProtocol.U_TONG_BING_SYNC_TEAM, builder, userId);
    }

    /**
     * 同步发光数据
     */
    private void syncLight() {
        List<Long> userIdList = new ArrayList<>();
        for (CrossTongBingRank rank : userRank.getRankList()) {
            if (userIdList.size() >= config.getLightRankNum() || rank.getValue() < config.getLightNeedZhanGong()) {
                break;
            }
            userIdList.add(Long.valueOf(rank.getId()));
        }

        // 获取在活动内的玩家
        long now = System.currentTimeMillis();
        List<Long> recUsers = userDataMap.values().stream().filter(item -> isInActivity(item, now)).map(CrossTongBingUserData::getUserId).collect(Collectors.toList());
        if (recUsers.isEmpty()) {
            return;
        }

        TongBingProto.TongBingSyncLightUserRespMsg.Builder builder = TongBingProto.TongBingSyncLightUserRespMsg.newBuilder();
        builder.addAllUserId(userIdList);
        sendPacketToAll(ClientProtocol.U_TONG_BING_SYNC_LIGHT_USER, builder, recUsers);
    }

    private void syncCityEventToAll() {
        TongBingProto.SyncTongBingCityEventRespMsg.Builder builder = TongBingProto.SyncTongBingCityEventRespMsg.newBuilder();
        for (CrossTongBingCityEvent cityEvent : getCrossTongBingData().getCityEventMap().values()) {
            builder.addEvent(parseTongBingCityEventMsg(cityEvent));
        }
        sendPacketToAll(ClientProtocol.U_TONG_BING_SYNC_CITY_EVENT, builder, userDataMap.keySet());
    }

    private void syncBuffToAll() {
        for (Map.Entry<Integer, List<CrossTongBingCampBuff>> entry : getCrossTongBingData().getCampBuffMap().entrySet()) {
            Integer campId = entry.getKey();
            TongBingProto.TongBingSyncBuffRespMsg.Builder builder = TongBingProto.TongBingSyncBuffRespMsg.newBuilder();
            for (CrossTongBingCampBuff buff : new ArrayList<>(entry.getValue())) {
                builder.addBuff(parseTongBingBuffMsg(buff));
            }
            List<Long> campUserList = userDataMap.values().stream().filter(item -> item.getCampId() == campId).mapToLong(CrossTongBingUserData::getUserId).boxed().collect(Collectors.toList());
            sendPacketToAll(ClientProtocol.U_TONG_BING_SYNC_BUFF, builder, campUserList);
        }
    }

    /**
     * 同步指挥官信息给阵营所有玩家
     *
     * @param campId
     */
    private void syncCommanderInfoToCamp(int campId) {
        if (campId == 0) {
            return;
        }
        TongBingProto.TongBingCommanderDataSyncMsg.Builder syncMsg = parseCommanderInfoMsg(campId);
        // 同步给阵营所有玩家
        List<Long> campUserList = userDataMap.values().stream().filter(item -> item.getCampId() == campId).mapToLong(CrossTongBingUserData::getUserId).boxed().collect(Collectors.toList());
        sendPacketToAll(ClientProtocol.U_TONG_BING_COMMANDER_DATA_SYNC, syncMsg, campUserList);
    }

    /**
     * 同步指挥官信息
     */
    private void syncCommanderInfo(long userId, int campId) {
        if (campId == 0) {
            return;
        }
        TongBingProto.TongBingCommanderDataSyncMsg.Builder syncMsg = parseCommanderInfoMsg(campId);
        sendPacket(ClientProtocol.U_TONG_BING_COMMANDER_DATA_SYNC, syncMsg, userId);
    }

    private TongBingProto.TongBingCommanderDataSyncMsg.Builder parseCommanderInfoMsg(int campId) {
        TongBingProto.TongBingCommanderDataSyncMsg.Builder syncMsg = TongBingProto.TongBingCommanderDataSyncMsg.newBuilder();
        TongBingProto.TongBingCommanderDataTemp.Builder commanderDataMsg = TongBingProto.TongBingCommanderDataTemp.newBuilder();
        CrossTongBingCampData crossTongBingCampData = campDataMap.get(campId);
        CrossTongBingCommanderData commanderData = crossTongBingCampData.getCommanderData();
        if (commanderData != null) {
            commanderDataMsg.setUserId(commanderData.getUserId());
            UserBaseInfo userBaseInfo = CrossUserMgr.getUserBaseInfo(commanderData.getUserId());
            if (userBaseInfo != null) {
                commanderDataMsg.setPlayerBaseData(PlayerBasePb.parsePlayerBaseTempMsg(userBaseInfo));
            }
            commanderDataMsg.setNotice(commanderData.getNotice());
            commanderDataMsg.setMajorCityId(commanderData.getMajorCityId());
            commanderDataMsg.setMinorCityId(commanderData.getMinorCityId());
            syncMsg.setCommanderData(commanderDataMsg);
        }
        return syncMsg;
    }

    // ============================== PB ==============================

    public TongBingProto.TongBingBuffMsg.Builder parseTongBingBuffMsg(CrossTongBingCampBuff buff) {
        TongBingProto.TongBingBuffMsg.Builder builder = TongBingProto.TongBingBuffMsg.newBuilder();
        builder.setBuffId(buff.getBuffId());
        builder.setCityId(buff.getCityId());
        builder.setEndTime(getBuffEndTime(buff));
        return builder;
    }

    public TongBingProto.TongBingCityEventMsg.Builder parseTongBingCityEventMsg(CrossTongBingCityEvent cityEvent) {
        TongBingProto.TongBingCityEventMsg.Builder builder = TongBingProto.TongBingCityEventMsg.newBuilder();
        builder.setCityId(cityEvent.getCityId());
        builder.setBuffId(cityEvent.getBuffId());
        builder.setRefreshTime(cityEvent.getRefreshTime());
        if (cityEvent.getEffectCamp() != 0) {
            builder.setEffectCampId(cityEvent.getEffectCamp());
            builder.setEndTime(cityEvent.getEndTime());
        }
        return builder;
    }

    public TongBingProto.TongBingTeamMsg.Builder parseTongBingTeamMsg(CrossTongBingUserData userData, CrossTongBingTeam team) {
        // 获取主将
        int patrons1 = 0;
        int skin1 = 0;
        if (!team.getPatronsList().isEmpty()) {
            patrons1 = team.getPatronsList().get(0);
            CrossTongBingPatrons patrons = userData.getPatronsMap().get(patrons1);
            if (patrons != null) {
                skin1 = patrons.getSkin();
            }
        }

        TongBingProto.TongBingTeamMsg.Builder builder = TongBingProto.TongBingTeamMsg.newBuilder();
        builder.setOccupation(team.getOccupation());
        builder.setPatronsId(patrons1);
        builder.setTeamStatus(getTeamStatus(team).getValue());
        builder.setCityId(team.getCityId());
        builder.addAllToCityId(new ArrayList<>(team.getRouteList()));
        builder.setStartTime(team.getBeginMoveTime());
        builder.setNextCityArriveTime(team.getArriveTime());
        builder.setLastCityDepartureTime(team.getSmallMoveTime());
        builder.setMaxSoldier(team.getMaxSoldier());
        builder.setSoldierNum(team.getSoldierNum());
        builder.setUser(parseTongBingUserMsg(userData.getUserId()));
        builder.setSkinId(skin1);
        builder.setHorseId(userData.getHorseId());
        builder.setTargetCityId(team.getTargetCityId());
        return builder;
    }

    public TongBingProto.TongBingCityMsg.Builder parseTongBingCityMsg(CrossTongBingCity city) {
        // 准备几个参数
        Set<Integer> atkCampId = new HashSet<>();
        int atkNum = 0;
        int defNum = 0;
        long helpNum = 0;

        CrossTongBingBattlefield battlefield = city.getBattlefield();
        if (battlefield != null) {
            helpNum = battlefield.getHelpNum();
            for (CrossTongBingBattlefieldPart part : battlefield.getPartMap().values()) {
                // 进攻、防守方数量
                atkNum += part.getAtkList().size();
                defNum += part.getDefList().size();
                if (atkCampId.size() < config.getCampNum() - 1) {
                    // 遍历攻击方找攻击方阵营
                    for (CrossTongBingBattlefieldTeam atkTempTeam : new LinkedList<>(part.getAtkList())) {
                        CrossTongBingTeamArrive arrive = atkTempTeam.getTeam();
                        if (arrive != null) {
                            atkCampId.add(getUserData(arrive.getUserId()).getCampId());
                        }
                    }
                }
            }
        }

        atkNum += city.getAtkTeamList().size();
        if (atkCampId.size() < config.getCampNum() - 1) {
            for (CrossTongBingTeamArrive arrive : new LinkedList<>(city.getAtkTeamList())) {
                atkCampId.add(getUserData(arrive.getUserId()).getCampId());
            }
        }

        defNum = defNum + city.getDefTeamList().size() + getSoldierTeamCount(city);

        TongBingProto.TongBingCityMsg.Builder builder = TongBingProto.TongBingCityMsg.newBuilder();
        builder.setCityId(city.getCityId());
        builder.setCampId(city.getCampId());
        builder.setDefTeamNum(defNum);
        builder.setAtkTeamNum(atkNum);
        builder.setNeedHelpNum(helpNum);
        builder.addAllAtkCampId(new ArrayList<>(atkCampId));
        builder.setTeamRecTime(city.getTeamRecoveryBeginTime() == 0 ? 0 : city.getTeamRecoveryBeginTime() + config.getCityTeamRecoveryTime());
        return builder;
    }

    public TongBingProto.TongBingCityDetailMsg.Builder parseTongBingCityDetailMsg(CrossTongBingCity city) {

        TongBingCityConfig cityConfig = config.getCityConfigMap().get(city.getCityId());
        TongBingCityTypeConfig typeConfig = config.getCityTypeConfigMap().get(cityConfig.getType());

        Map<Long, CrossTongBingDefUser> defUserMap = new HashMap<>();
        int defTeamNum = city.getTeamNum(); // 城防军数量
        int extraTeamNum = city.getExtraTeamNum(); // 额外的城防军数量
        int defUserTeamNum = 0; // 玩家队伍数量

        // 战场内拿防守队伍
        CrossTongBingBattlefield battlefield = city.getBattlefield();
        if (battlefield != null) {
            for (CrossTongBingBattlefieldPart part : battlefield.getPartMap().values()) {
                for (CrossTongBingBattlefieldTeam battlefieldTeam : new LinkedList<>(part.getDefList())) {
                    if (battlefieldTeam.getTeam() == null) {
                        if (battlefieldTeam.getSoldierType() == eTongBingSoldierType.COMMON_SOLDIER.getValue()) {
                            defTeamNum++;
                        } else if (battlefieldTeam.getSoldierType() == eTongBingSoldierType.EXTRA_SOLDIER.getValue()) {
                            extraTeamNum++;
                        }
                    } else {
                        CrossTongBingTeam team = getTeamByArrive(battlefieldTeam.getTeam());
                        if (!defUserMap.containsKey(team.getUserId())) {
                            defUserMap.put(team.getUserId(), new CrossTongBingDefUser(team.getUserId()));
                        }
                        CrossTongBingDefUser defUser = defUserMap.get(team.getUserId());
                        defUser.setTeamNum(defUser.getTeamNum() + 1);
                        defUser.setSoldierNum(defUser.getSoldierNum() + team.getSoldierNum());
                    }
                }
            }
        }
        // 排队里拿防守队伍
        for (CrossTongBingTeamArrive arrive : new LinkedList<>(city.getDefTeamList())) {
            CrossTongBingTeam team = getTeamByArrive(arrive);
            if (!defUserMap.containsKey(team.getUserId())) {
                defUserMap.put(team.getUserId(), new CrossTongBingDefUser(team.getUserId()));
            }
            CrossTongBingDefUser defUser = defUserMap.get(team.getUserId());
            defUser.setTeamNum(defUser.getTeamNum() + 1);
            defUser.setSoldierNum(defUser.getSoldierNum() + team.getSoldierNum());
        }

        // 计算防守玩家队伍数
        for (CrossTongBingDefUser defUser : defUserMap.values()) {
            defUserTeamNum += defUser.getTeamNum();
        }

        // 是否当前阵营的出生城池
        boolean isBirthCity = cityConfig.getType() == 1 && cityConfig.getInitialCamp() == city.getCampId();

        // 防守队伍设置个显示上限
        List<CrossTongBingDefUser> sortDefUserList = new ArrayList<>();
        if (!isBirthCity) {
            sortDefUserList = defUserMap.values().stream().sorted(Comparator.comparing(CrossTongBingDefUser::getSoldierNum).reversed())
                    .limit(50).collect(Collectors.toList()); // 写死50个
        }

        // 返回值
        TongBingProto.TongBingCityDetailMsg.Builder builder = TongBingProto.TongBingCityDetailMsg.newBuilder();
        builder.setDefTeamNum(defTeamNum);
        builder.setExtraDefTeamNum(extraTeamNum);
        builder.setDefTeamSoldierNum(city.getTeamSoldierNum());
        builder.setFoodProduction(typeConfig.getFoodProduction());
        for (CrossTongBingDefUser tmpDefTeam : sortDefUserList) {
            UserBaseInfo userBaseInfo = CrossUserMgr.getUserBaseInfo(tmpDefTeam.getUserId());
            String nickname = userBaseInfo == null ? "" : userBaseInfo.getNickName();

            TongBingProto.TongBingCityDefUserMsg.Builder tmp = TongBingProto.TongBingCityDefUserMsg.newBuilder();
            tmp.setUserId(tmpDefTeam.getUserId());
            tmp.setName(nickname);
            tmp.setTeamNum(tmpDefTeam.getTeamNum());
            tmp.setTotalValue(tmpDefTeam.getSoldierNum());

            builder.addDefUser(tmp);
        }
        builder.setTotalTeamNum(defTeamNum + extraTeamNum + defUserTeamNum);
        return builder;
    }

    public TongBingProto.TongBingBarracksMsg.Builder parseTongBingBarracksMsg(long userId) {
        TongBingProto.TongBingBarracksMsg.Builder builder = TongBingProto.TongBingBarracksMsg.newBuilder();
        CrossTongBingUserData userData = getUserData(userId);
        CrossTongBingBarracks barracks = userData.getBarracks();
        builder.setReadyNum(barracks.getReadyNum());
        builder.setReadyMaxNum(barracks.getReadyMaxNum());
        builder.setCreateNum(barracks.getCreateNum());
        builder.setCreateTime(barracks.getCreateTime());
        builder.setCreateSpeed(barracks.getCreateSpeed() == 0 ? 0 : DateHelper.SECOND_MILLIONS / barracks.getCreateSpeed());
        builder.setCreateCount(barracks.getCreateCount());
        builder.setWoundedNum(barracks.getWoundedNum());
        builder.setWoundedMaxNum(barracks.getHospitalSize());
        builder.setRescueNum(barracks.getRescueNum());
        builder.setRescueTime(barracks.getRescueTime());
        builder.setRescueSpeed(barracks.getRescueSpeed());
        builder.setRescueCount(barracks.getRescueCount());

        return builder;
    }

    public TongBingProto.TongBingCityWarReportMsg.Builder parseTongBingCityWarReportMsg(CrossTongBingCityWarReport report) {
        TongBingProto.TongBingCityWarReportMsg.Builder builder = TongBingProto.TongBingCityWarReportMsg.newBuilder();
        builder.addAllAtkCampId(report.getAtkCampId());
        builder.setDefCampId(report.getDefCampId());
        builder.setType(report.getType());
        builder.setCityId(report.getCityId());
        builder.setTime(report.getTime());
        for (CrossTongBingBattlefieldRank rank : report.getAtkRankList()) {
            builder.addAtkRank(parseTongBingBattlefieldRankMsg(rank));
        }
        for (CrossTongBingBattlefieldRank rank : report.getDefRankList()) {
            builder.addDefRank(parseTongBingBattlefieldRankMsg(rank));
        }
        builder.setWinCampId(report.getWinCampId());
        return builder;
    }

    public int getDefTeamNum(CrossTongBingCity city, CrossTongBingBattlefield battlefield, int type) {
        int num;
        if (type == eTongBingSoldierType.COMMON_SOLDIER.getValue()) {
            num = city.getTeamNum();
        } else if (type == eTongBingSoldierType.EXTRA_SOLDIER.getValue()) {
            num = city.getExtraTeamNum();
        } else {
            return 0;
        }
        for (CrossTongBingBattlefieldPart crossTongBingBattlefieldPart : battlefield.getPartMap().values()) {
            // 统计守军数量
            for (CrossTongBingBattlefieldTeam team : new LinkedList<>(crossTongBingBattlefieldPart.getDefList())) {
                if (team.getTeam() == null) {
                    if (team.getSoldierType() == type) {
                        num++;
                    }
                }
            }
        }
        return num;
    }

    public TongBingProto.TongBingBattlefieldMsg.Builder parseTongBingBattlefieldMsg(CrossTongBingBattlefieldPart part,
                                                                                    CrossTongBingBattlefield battlefield,
                                                                                    CrossTongBingCity city) {

        int cityDefTeamNum = city.getTeamNum();
        int cityExtraDefTeamNum = city.getExtraTeamNum();
        int defNum = city.getDefTeamList().size() + city.getTeamNum() + city.getExtraTeamNum();
        int atkNum = city.getAtkTeamList().size();
        for (CrossTongBingBattlefieldPart crossTongBingBattlefieldPart : battlefield.getPartMap().values()) {
            defNum += crossTongBingBattlefieldPart.getDefList().size();
            atkNum += crossTongBingBattlefieldPart.getAtkList().size();
            // 统计守军数量
            for (CrossTongBingBattlefieldTeam team : new LinkedList<>(crossTongBingBattlefieldPart.getDefList())) {
                if (team.getTeam() == null) {
                    if (team.getSoldierType() == eTongBingSoldierType.COMMON_SOLDIER.getValue()) {
                        cityDefTeamNum++;
                    } else if (team.getSoldierType() == eTongBingSoldierType.EXTRA_SOLDIER.getValue()) {
                        cityExtraDefTeamNum++;
                    }
                }
            }
        }


        TongBingProto.TongBingBattlefieldMsg.Builder builder = TongBingProto.TongBingBattlefieldMsg.newBuilder();
        builder.setPartId(part.getPartId());
        int defNo = part.getDefDeadCount();
        for (CrossTongBingBattlefieldTeam team : new LinkedList<>(part.getDefList())) {
            builder.addDef(parseTongBingBattlefieldTeamMsg(city, team, ++defNo));
        }
        int atkNo = part.getAtkDeadCount();
        for (CrossTongBingBattlefieldTeam team : new LinkedList<>(part.getAtkList())) {
            builder.addAtk(parseTongBingBattlefieldTeamMsg(city, team, ++atkNo));
        }

        int rankNum = 0;
        for (CrossTongBingBattlefieldRank rank : new ArrayList<>(battlefield.getBattlefieldRankNewList())) {
            if (rankNum++ > 5) {
                break;
            }
            builder.addRank(parseTongBingBattlefieldRankMsg(rank));
        }

        builder.setBattleNo(city.getBattledNum() + 1);
        builder.setBattleTime(battlefield.getCreateTime() + config.getBattlePrepareTime());
        builder.setDrumTimes(battlefield.getDrumTimes());
        builder.addAllPartList(battlefield.getPartMap().keySet());
        builder.setCityId(city.getCityId());
        builder.setWinType(part.getWinType());
        builder.setDefNum(defNum);
        builder.setAtkNum(atkNum);
        builder.setCityDefTeamNum(cityDefTeamNum);
        builder.setCityExtraDefTeamNum(cityExtraDefTeamNum);
        return builder;
    }

    public TongBingProto.TongBingBattlefieldRankMsg.Builder parseTongBingBattlefieldRankMsg(CrossTongBingBattlefieldRank rank) {
        TongBingProto.TongBingBattlefieldRankMsg.Builder builder = TongBingProto.TongBingBattlefieldRankMsg.newBuilder();
        builder.setUser(parseTongBingUserMsg(rank.getUserId()));
        builder.setValue(rank.getZhanGong());
        builder.setKillNum(rank.getKillNum());
        return builder;
    }

    public TongBingProto.TongBingBattlefieldTeamMsg.Builder parseTongBingBattlefieldTeamMsg(CrossTongBingCity city, CrossTongBingBattlefieldTeam battlefieldTeam, int no) {
        TongBingProto.TongBingBattlefieldTeamMsg.Builder builder = TongBingProto.TongBingBattlefieldTeamMsg.newBuilder();
        builder.setTeamNo(no);

        if (battlefieldTeam.getTeam() != null) {
            CrossTongBingTeamArrive arrive = battlefieldTeam.getTeam();
            CrossTongBingTeam team = getTeamByArrive(arrive);
            CrossTongBingUserData userData = getUserData(arrive.getUserId());
            int patronsId = team.getPatronsList().isEmpty() ? 0 : team.getPatronsList().get(0);
            int skinId = userData.getPatronsMap().containsKey(patronsId) ? userData.getPatronsMap().get(patronsId).getSkin() : 0;

            builder.setUser(parseTongBingUserMsg(team.getUserId()));
            builder.setSoldierNum(Math.max(team.getSoldierNum(), 1)); // 战场内最少1个兵，不然客户端显示有问题
            builder.setPatronsId(patronsId);
            builder.setSkinId(skinId);
            builder.setSoldierMaxNum(team.getMaxSoldier());
        } else {
            // builder.setUser();
            builder.setSoldierNum(Math.max(battlefieldTeam.getSoldierNum(), 1)); // 战场内最少1个兵，不然客户端显示有问题
            builder.setPatronsId(0);
            builder.setSkinId(0);
            builder.setSoldierMaxNum(city.getTeamSoldierNum());
        }

        return builder;
    }

    public TongBingProto.TongBingUserMsg.Builder parseTongBingUserMsg(long userId) {
        CrossTongBingUserData userData = getUserData(userId);
        UserBaseInfo userBaseInfo = CrossUserMgr.getUserBaseInfo(userId);
        String nickname = userBaseInfo == null ? "" : userBaseInfo.getNickName();

        TongBingProto.TongBingUserMsg.Builder builder = TongBingProto.TongBingUserMsg.newBuilder();
        builder.setUserId(userId);
        builder.setCampId(userData.getCampId());
        builder.setNickname(nickname);
        return builder;
    }

    public TongBingProto.TongBingCampRankMsg.Builder parseTongBingCampRankMsg(CrossTongBingRank rank) {
        Integer campId = Integer.valueOf(rank.getId());

        // 拿一下阵营第一的昵称
        UserBaseInfo userBaseInfo = getCampMaxZhanGongUserBaseInfo(campId);
        String nickname = userBaseInfo == null ? "" : userBaseInfo.getNickName();

        TongBingProto.TongBingCampRankMsg.Builder builder = TongBingProto.TongBingCampRankMsg.newBuilder();
        builder.setCampId(campId);
        builder.setValue(rank.getValue());
        builder.setMaxNickname(nickname);
        return builder;
    }

    public TongBingProto.TongBingCampUnionRankMsg.Builder parseTongBingCampUnionRankMsg(CrossTongBingRank rank) {
        String unionUid = rank.getId();
        CrossTongBingUnionJoin crossTongBingUnionJoin = unionJoinMap.get(unionUid);
        UnionBaseInfo unionBaseInfo = crossTongBingUnionJoin == null ? new UnionBaseInfo() : crossTongBingUnionJoin.getUnionBaseInfo();
        TongBingProto.TongBingCampUnionRankMsg.Builder builder = TongBingProto.TongBingCampUnionRankMsg.newBuilder();
        builder.setUnionUid(unionUid);
        builder.setValue(rank.getValue());
        builder.setUnionInfo(UnionBasePb.parseUnionBaseTempMsg(unionBaseInfo));
        builder.setCampId(crossTongBingUnionJoin == null ? 0 : crossTongBingUnionJoin.getCampId());
        return builder;
    }

    public TongBingProto.TongBingUserRankMsg.Builder parseTongBingUserRankMsg(CrossTongBingRank rank) {
        Long userId = Long.valueOf(rank.getId());
        UserBaseInfo userBaseInfo = CrossUserMgr.getUserBaseInfo(userId);
        if (userBaseInfo == null) {
            userBaseInfo = new UserBaseInfo();
        }
        int campId = getUserData(userId).getCampId();

        TongBingProto.TongBingUserRankMsg.Builder builder = TongBingProto.TongBingUserRankMsg.newBuilder();
        builder.setUserId(userId);
        builder.setPlayerBaseData(PlayerBasePb.parsePlayerBaseTempMsg(userBaseInfo));
        builder.setValue(rank.getValue());
        builder.setCampId(campId);
        return builder;
    }

    public TongBingProto.TongBingRedPacketMsg.Builder parseTongBingRedPacketMsg(CrossTongBingRedPacket redPacket, boolean getReward) {
        UserBaseInfo userBaseInfo = CrossUserMgr.getUserBaseInfo(redPacket.getUserId());
        if (userBaseInfo == null) {
            userBaseInfo = new UserBaseInfo();
        }

        TongBingProto.TongBingRedPacketMsg.Builder tempMsg = TongBingProto.TongBingRedPacketMsg.newBuilder();
        tempMsg.setUserId(redPacket.getUserId());
        tempMsg.setNickName(userBaseInfo.getNickName());
        tempMsg.setUnionName(userBaseInfo.getUnionName());
        tempMsg.setType(redPacket.getGoodId());
        tempMsg.setOrderNo("" + redPacket.getRedPacketNo());
        tempMsg.setIsGet(getReward ? 1 : 0);
        return tempMsg;
    }

    // ============================== 聊天 ==============================

    /**
     * 添加系统聊天通知
     *
     * @param sendTime 毫秒，进来再转秒
     */
    private void addSystemChat(eMessageType messageType, int id, String content, long sendTime) {
        addSystemChat(messageType, null, id, content, sendTime);
    }

    /**
     * 添加系统聊天通知
     *
     * @param sendTime 毫秒，进来再转秒
     */
    private void addSystemChat(eMessageType messageType, Long userId, int id, String content, long sendTime) {
        // 转换秒
        sendTime = sendTime / 1000;

        int range = 0;
        if (messageType == eMessageType.TongBingPublicBattleChat) {
            range = 1;
        } else if (messageType == eMessageType.TongBingCampHelpChat) {
            range = 2;
        } else if (messageType == eMessageType.TongBingBattlefieldWinChat) {
            range = 3;
        }

        if (range <= 0) {
            return;
        }

        WorldMessageProto.WorldMessageTempMsg.Builder builder = WorldMessageProto.WorldMessageTempMsg.newBuilder();
        builder.setType(messageType.getValue());
        builder.setSendTime(sendTime);
        builder.setContent(content);
        builder.setActivityId(config.getActivityInfo().getActivityId());
        if (userId != null) {
            UserBaseInfo userBaseInfo = CrossUserMgr.getUserBaseInfo(userId);
            if (userBaseInfo != null) {
                builder.setUserId(userId);
                builder.setPlayerBaseData(PlayerBasePb.parsePlayerBaseTempMsg(userBaseInfo));
            }
        }
        chatLogic.chat(range, id, 0, builder.build());
    }

    public void syncChat(int range, int id, WorldMessageProto.WorldMessageTempMsg chatMsg) {
        long now = System.currentTimeMillis();
        List<Long> userIdList = new ArrayList<>();
        if (range == CrossTongBingChatLogic.RANGE_ALL) {
            userIdList = userDataMap.values().stream().filter(item -> isInActivity(item, now)).mapToLong(item -> item.getUserId()).boxed().collect(Collectors.toList());
        } else if (range == CrossTongBingChatLogic.RANGE_CAMP) {
            userIdList = userDataMap.values().stream().filter(item -> item.getCampId() == id && isInActivity(item, now)).mapToLong(item -> item.getUserId()).boxed().collect(Collectors.toList());
        } else if (range == CrossTongBingChatLogic.RANGE_BATTLEFIELD) {
            CrossTongBingBattlefield battlefield = getCrossTongBingData().getCityMap().get(id).getBattlefield();
            if (battlefield != null) {
                for (Integer partId : battlefield.getPartMap().keySet()) {
                    List<Long> list = CrossSceneMgr.getSceneUserList(config.getActivityInfo().getActivityId(),
                            eSceneType.TongBingBattlefield.getValue(),
                            getBattlefieldSceneKey(id, partId),
                            23333)
                            .stream().mapToLong(item -> item.getUserId()).boxed().collect(Collectors.toList());
                    userIdList.addAll(list);
                }
            }
        }
        if (!userIdList.isEmpty()) {
            sendPacketToAll(ClientProtocol.U_WORLD_MESSAGE_SYNC, chatMsg.toBuilder(), userIdList);
        }
    }

    // ============================== 其他 ==============================

    public void save() {
        if (crossTongBingData.isInsertOption()) {
            TongBingActivityBusiness.addCrossTongBingData(crossTongBingData);
        } else if (crossTongBingData.isUpdateOption()) {
            TongBingActivityBusiness.updateCrossTongBingData(crossTongBingData);
        }

        for (CrossTongBingUnionJoin join : unionJoinMap.values()) {
            if (join.isInsertOption()) {
                TongBingActivityBusiness.addCrossTongBingUnionJoin(join);
            } else if (join.isUpdateOption()) {
                TongBingActivityBusiness.updateCrossTongBingUnionJoin(join);
            }
        }

        for (CrossTongBingUserData data : userDataMap.values()) {
            if (data.isInsertOption()) {
                TongBingActivityBusiness.addCrossTongBingUserData(data);
            } else if (data.isUpdateOption()) {
                TongBingActivityBusiness.updateCrossTongBingUserData(data);
            }
        }

        List<CrossTongBingCityWarReport> tempNeedInsertCityReportList;
        synchronized (lock) {
            tempNeedInsertCityReportList = new ArrayList<>(needInsertCityReportList);
            needInsertCityReportList.clear();
        }
        if (tempNeedInsertCityReportList.size() > 0) {
            TongBingActivityBusiness.addCityReportList(tempNeedInsertCityReportList);
        }

        List<CrossTongBingUserWarReport> tempNeedInsertUserReportList;
        synchronized (lock) {
            tempNeedInsertUserReportList = new ArrayList<>(needInsertUserReportList);
            needInsertUserReportList.clear();
        }
        if (tempNeedInsertUserReportList.size() > 0) {
            TongBingActivityBusiness.addUserReportList(tempNeedInsertUserReportList);
        }

        for (CrossTongBingCampData campData : campDataMap.values()) {
            if (campData.isInsertOption()) {
                TongBingActivityBusiness.addCrossTongBingCampData(campData);
            } else if (campData.isUpdateOption()) {
                TongBingActivityBusiness.updateCrossTongBingCampData(campData);
            }
        }
    }

    /**
     * 玩家是否在界面内
     */
    public boolean isInActivity(CrossTongBingUserData data, long now) {
        if (!isAble(data)) {
            return false; // 无资格的都进不了界面
        }
        if (now - data.getLastReqTime() < DateHelper.MINUTE_MILLIONS * 3) {
            return true; // 3分钟内有请求的都当他在线
        }
        return false;
    }

    /**
     * 设置请求时间
     */
    public void setLastReqTime(long userId) {
        if (userId <= 0) {
            return;
        }
        CrossTongBingUserData userData = getUserData(userId);
        userData.setLastReqTime(System.currentTimeMillis());
    }

    public void printTask() {
        Map<String, Integer> countMap = new ConcurrentHashMap<>();
        for (BaseCrossTongBingTask task : taskQueue) {
            countMap.put(task.getType().name(), countMap.getOrDefault(task.getType().name(), 0) + 1);
        }
        logger.info("【统兵演武任务】 - {}", JSON.toJSONString(countMap));
    }

    public String getBattlefieldSceneKey(int cityId, int partId) {
        return groupId + "-" + cityId + "-" + partId;
    }

    public void syncNeedSyncTeam() {
        // 拿数据
        List<String> teamKeyList;
        synchronized (needSyncTeam) {
            teamKeyList = new ArrayList<>(needSyncTeam);
            needSyncTeam.clear();
        }
        // 没数据不同步
        if (teamKeyList.isEmpty()) {
            return;
        }
        // 拿在游戏内玩家
        long now = System.currentTimeMillis();
        List<Long> userList = userDataMap.values().stream().filter(item -> isInActivity(item, now)).map(item -> item.getUserId()).collect(Collectors.toList());
        if (userList.isEmpty()) {
            return;
        }
        // 转化格式
        List<CrossTongBingTeam> teamList = new ArrayList<>();
        for (String teamKey : teamKeyList) {
            teamList.add(getTeamByKey(teamKey));
        }
        // 同步
        TongBingProto.TongBingSyncTeamRespMsg.Builder builder = TongBingProto.TongBingSyncTeamRespMsg.newBuilder();
        for (CrossTongBingTeam team : teamList) {
            builder.addTeam(parseTongBingTeamMsg(getUserData(team.getUserId()), team));
        }
        sendPacketToAll(ClientProtocol.U_TONG_BING_SYNC_TEAM, builder, userList);
    }

    public void addNeedSyncTeam(CrossTongBingTeam team) {
        String key = getTeamKey(team);
        synchronized (needSyncTeam) {
            needSyncTeam.add(key);
            // 20个就提前同步
            if (needSyncTeam.size() > 20) {
                syncNeedSyncTeam();
            }
        }
    }

    public void addNeedSyncCity(int cityId) {
        synchronized (needSyncCity) {
            needSyncCity.add(cityId);
            // 20个就提前同步
            if (needSyncCity.size() > 20) {
                syncNeedSyncCity();
            }
        }
    }

    public void syncNeedSyncCity() {
        // 拿数据
        List<Integer> cityIdList;
        synchronized (needSyncCity) {
            cityIdList = new ArrayList<>(needSyncCity);
            needSyncCity.clear();
        }
        // 没数据不同步
        if (cityIdList.isEmpty()) {
            return;
        }
        // 拿在游戏内玩家
        long now = System.currentTimeMillis();
        List<Long> userList = userDataMap.values().stream().filter(item -> isInActivity(item, now)).map(item -> item.getUserId()).collect(Collectors.toList());
        if (userList.isEmpty()) {
            return;
        }
        // 转化格式
        List<CrossTongBingCity> cityList = new ArrayList<>();
        Map<Integer, CrossTongBingCity> cityMap = getCrossTongBingData().getCityMap();
        for (int cityId : cityIdList) {
            cityList.add(cityMap.get(cityId));
        }
        // 同步
        TongBingProto.TongBingSyncCityRespMsg.Builder builder = TongBingProto.TongBingSyncCityRespMsg.newBuilder();
        for (CrossTongBingCity city : cityList) {
            builder.addCity(parseTongBingCityMsg(city));
        }
        sendPacketToAll(ClientProtocol.U_TONG_BING_SYNC_CITY, builder, userList);
    }

    public String getTeamKey(CrossTongBingTeam team) {
        return team.getUserId() + "-" + team.getOccupation();
    }

    public String getTeamKey(CrossTongBingTeamArrive arrive) {
        return arrive.getUserId() + "-" + arrive.getTeamId();
    }

    public CrossTongBingTeam getTeamByKey(String key) {
        String[] split = key.split("-");
        CrossTongBingUserData userData = getUserData(Long.valueOf(split[0]));
        return userData.getTeamMap().get(Integer.valueOf(split[1]));
    }

    public List<Long> getBattlefieldUserIdList(int cityId, int partId) {
        String sceneKey = getBattlefieldSceneKey(cityId, partId);
        List<UserScene> sceneUserList = CrossSceneMgr.getSceneUserList(
                config.getActivityInfo().getActivityId(),
                eSceneType.TongBingBattlefield.getValue(),
                sceneKey,
                23333);
        return sceneUserList.stream().mapToLong(UserScene::getUserId).boxed().collect(Collectors.toList());
    }

    /**
     * 领取榜单奖励
     */
    private int getRankReward(long userId, int conditionType) {
        CrossTongBingUserData userData = getUserData(userId);

        // 资格
        if (!isAble(userData)) {
            return GameErrorCode.E_TONG_BING_NO_ABLE;
        }

        // 拿职位参数
        CrossTongBingUnionJoin join = unionJoinMap.get(userData.getUnionUid());
        CrossTongBingUnionJoinMember member = join.getMemberMap().get(userId);
        int position = member.getPosition();

        // 拿阵营排名参数
        LinkedList<CrossTongBingRank> campRankList = getCampScoreRankList();
        String myCampId = String.valueOf(userData.getCampId());
        int myCampRank = -1;
        int campRankNum = 0;
        for (CrossTongBingRank rank : campRankList) {
            campRankNum++;
            if (Objects.equals(rank.getId(), myCampId)) {
                myCampRank = campRankNum;
                break;
            }
        }

        // 拿榜单
        String myId;
        LinkedList<CrossTongBingRank> rankList;
        if (conditionType == eGamePlayerEventType.TongBingUserZhanGongRank.getValue()) {
            rankList = userRank.getRankList();
            myId = String.valueOf(userId);
        } else if (conditionType == eGamePlayerEventType.TongBingCampUnionZhanGongRank.getValue()) {
            CrossTongBingRankListModel unionRank = campUnionRankMap.get(userData.getCampId());
            rankList = unionRank == null ? new LinkedList<>() : unionRank.getRankList();
            myId = userData.getUnionUid();
        } else if (conditionType == eGamePlayerEventType.TongBingCampScoreRank.getValue()) {
            rankList = campRankList;
            myId = String.valueOf(userData.getCampId());
        } else {
            return GameErrorCode.E_TONG_BING_PARAM_ERR;
        }

        // 【拿榜单排名】
        int myRank = -1;
        int num = 0;
        for (CrossTongBingRank rank : rankList) {
            num++;
            if (Objects.equals(rank.getId(), myId)) {
                myRank = num;
                break;
            }
        }

        TonBingProto.CrossTonBingGetRankRewardRespMsg.Builder builder = TonBingProto.CrossTonBingGetRankRewardRespMsg.newBuilder();
        builder.setRet(0);
        builder.setActivityId(config.getActivityInfo().getActivityId());
        builder.setConditionType(conditionType);
        builder.setPosition(position);
        builder.setRank(myRank);
        builder.setCampRank(myCampRank);
        builder.setUnionId(myId);
        sendPacket(GameProtocol.S_TONG_BING_GET_RANK_REWARD_FROM_CROSS, builder, userId);
        return 0;
    }

    /**
     * 通知游戏服condition更新值
     */
    private void notifyConditionChange(long userId, eGamePlayerEventType type, long value) {
        if (type != eGamePlayerEventType.TongBingZhanGong) {
            return;
        }

        TongBingProto.CrossTongBingNotifyConditionRespMsg.Builder builder = TongBingProto.CrossTongBingNotifyConditionRespMsg.newBuilder();
        builder.setUserId(userId);
        builder.setConditionType(type.getValue());
        builder.setValue(value);
        sendPacket(GameProtocol.S_TONG_BING_NOTIFY_CONDITION_FROM_CROSS, builder, userId);
    }

    public CrossTongBingRankListModel getUserRank() {
        return userRank;
    }

    public Map<Integer, CrossTongBingRankListModel> getCampUnionRankMap() {
        return campUnionRankMap;
    }

    public Map<String, CrossTongBingUnionJoin> getUnionJoinMap() {
        return unionJoinMap;
    }

    /**
     * 同步游戏内顶部消息
     *
     * @param type        1攻占成功，2防守成功
     * @param battlefield 可以为空
     */
    public void syncTopMsg(int campId, int cityId, int type, CrossTongBingBattlefield battlefield, int defCampId) {

        // 拿在界面内玩家
        long now = System.currentTimeMillis();
        Set<Long> userIdSet = new HashSet<>(
                userDataMap.values().stream().filter(item -> isInActivity(item, now)).map(item -> item.getUserId()).collect(Collectors.toList())
        );

        // 战场兼容null
        if (battlefield != null) {
            // 有战功的单独同步
            for (CrossTongBingBattlefieldRank rank : battlefield.getBattlefieldRankNewList()) {
                if (rank.getZhanGong() > 0 && userIdSet.contains(rank.getUserId())) {

                    TongBingProto.TongBingSyncTopMsgRespMsg.Builder builder = TongBingProto.TongBingSyncTopMsgRespMsg.newBuilder();
                    builder.setCampId(campId);
                    builder.setCityId(cityId);
                    builder.setType(type);
                    builder.setGetZhanGong(rank.getZhanGong());
                    builder.setDefCampId(defCampId);
                    sendPacket(ClientProtocol.U_TONG_BING_SYNC_TOP_MSG, builder, rank.getUserId());

                    // 记得移除单独同步玩家
                    userIdSet.remove(rank.getUserId());
                }
            }
        }

        // 其余的批量同步
        TongBingProto.TongBingSyncTopMsgRespMsg.Builder builder = TongBingProto.TongBingSyncTopMsgRespMsg.newBuilder();
        builder.setCampId(campId);
        builder.setCityId(cityId);
        builder.setType(type);
        builder.setGetZhanGong(0);
        builder.setDefCampId(defCampId);
        sendPacketToAll(ClientProtocol.U_TONG_BING_SYNC_TOP_MSG, builder, userIdSet);
    }

    public void changeHorse(long userId, int horseId) {
        CrossTongBingUserData userData = getUserData(userId);
        synchronized (lock) {

            if (horseId <= userData.getHorseId()) {
                logger.info("tong bing change horse fail! activityId {} userId {} horseId {} newHorseId {}.",
                        config.getActivityInfo().getActivityId(), userData.getUserId(), userData.getHorseId(), horseId);
                return;
            }

            // 换马
            userData.setHorseId(horseId);

            // 没在活动时间内
            long now = System.currentTimeMillis();
            if (now < config.getStage1TimeList().get(0).get(0) || now > config.getActivityInfo().getEndTime() * 1000L) {
                return;
            }

            // 同步
            for (CrossTongBingTeam team : userData.getTeamMap().values()) {
                if (getTeamStatus(team) == eTongBingTeamStatus.Move) {
                    addNeedSyncTeam(team);
                }
            }
        }
    }

    /**
     * 邮件补发辎重
     */
    public void mailSendCityCollect() {

        // 没到领奖期，不发
        if (System.currentTimeMillis() < config.getActivityInfo().getEndTime() * 1000) {
            return;
        }

        synchronized (lock) {

            // 已经发过
            if (getCrossTongBingData().getAutoSendCollect() == 1) {
                return;
            }

            // 重新计算辎重
            for (int campId = 1; campId <= config.getCampNum(); campId++) {
                makeAllCityCollect(campId, config.getActivityInfo().getEndTime() * 1000L);
            }

            // 统计全部玩家数据
            Map<Long, Map<Long, Long>> serverUserCountMap = new ConcurrentHashMap<>();
            for (CrossTongBingUserData userData : userDataMap.values()) {
                long userId = userData.getUserId();
                // 没资格
                if (!isAble(userData)) {
                    continue;
                }
                // 拿数量
                long userAllValue = 0L;
                CrossTongBingCampData campData = campDataMap.get(userData.getCampId());
                if (campData != null) {
                    Map<Long, Map<Integer, Long>> userCityValueMap = campData.getCollectionMap();
                    Map<Integer, Long> cityValueMap = userCityValueMap.getOrDefault(userData.getUserId(), new ConcurrentHashMap<>());
                    for (Long value : cityValueMap.values()) {
                        userAllValue += value;
                    }
                    userCityValueMap.put(userId, new ConcurrentHashMap<>());
                }
                // 统计值
                if (userAllValue > 0) {
                    UserBaseInfo userBaseInfo = CrossUserMgr.getUserBaseInfo(userId);
                    if (userBaseInfo == null) {
                        logger.info("mailSendCityCollect can not find userInfo. activityId {} userId {} count {}.",
                                config.getActivityInfo().getActivityId(), userId, userAllValue);
                    } else {
                        serverUserCountMap.putIfAbsent(userBaseInfo.getServerId(), new ConcurrentHashMap<>());
                        serverUserCountMap.get(userBaseInfo.getServerId()).put(userId, userAllValue);
                    }
                }
            }

            // 日志
            logger.error("tong bing activityId {} , mailSendCityCollect : {}",
                    config.getActivityInfo().getActivityId(), JSON.toJSONString(serverUserCountMap));

            // 改状态
            getCrossTongBingData().setAutoSendCollect(1);
            for (CrossTongBingCampData campData : campDataMap.values()) {
                campData.setUpdateOption();
            }

            // 按区服发奖
            for (Map.Entry<Long, Map<Long, Long>> entry : serverUserCountMap.entrySet()) {
                Long serverId = entry.getKey();
                TongBingProto.CrossTongBingMailSendCityCollectReqMsg.Builder builder = TongBingProto.CrossTongBingMailSendCityCollectReqMsg.newBuilder();
                for (Map.Entry<Long, Long> longEntry : entry.getValue().entrySet()) {
                    long userId = longEntry.getKey();
                    Long count = longEntry.getValue();
                    TongBingProto.CrossTongBingCityCollectMsg.Builder tmp = TongBingProto.CrossTongBingCityCollectMsg.newBuilder();
                    tmp.setUserId(userId);
                    tmp.setCount(count);
                    builder.addList(tmp);
                }
                YanQuMessage message = YanQuMessageUtils.buildMessage(GameProtocol.S_TONG_BING_AUTO_SEND_CITY_COLLECT, builder);
                MessageHelper.sendPacket(serverId, 0, message);
            }

        }
    }

    /**
     * 获取阵营最高战功的玩家
     */
    private UserBaseInfo getCampMaxZhanGongUserBaseInfo(int campId) {
        long userId = getCampMaxZhanGongUserId(campId);
        return userId == 0 ? null : CrossUserMgr.getUserBaseInfo(userId);
    }

    /**
     * 获取阵营最高战功的玩家Id
     */
    private long getCampMaxZhanGongUserId(int campId) {
        long userId = 0;
        CrossTongBingRankListModel rankListModel = campUserRankMap.get(campId);
        if (rankListModel != null) {
            LinkedList<CrossTongBingRank> rankList = rankListModel.getRankList();
            if (!rankList.isEmpty()) {
                CrossTongBingRank first = rankList.getFirst();
                userId = Long.parseLong(first.getId());
            }
        }
        return userId;
    }

    //传入最低排名获得前几名商会Uid
    private List<String> getTopUnions(int lowestRewardRank) {
        List<CrossTongBingRank> rankList = new ArrayList<>();
        for (CrossTongBingRankListModel model : campUnionRankMap.values()) {
            if (model != null) {
                rankList.addAll(model.getRankList());
            }
        }
        rankList.sort((o1, o2) -> {
            Long num1 = o1.getValue();
            Long num2 = o2.getValue();
            return num2.compareTo(num1);
        });

        List<String> unionUidList = new ArrayList<>();
        //找出top n的Uid 按照排名顺序塞入List
        for (int num = 0; num < lowestRewardRank && num < rankList.size(); num++) {
            unionUidList.add(rankList.get(num).getId());
        }
        return unionUidList;
    }


    // ========= 预留方法 =========

    public void reloadConfig() {
        loadConfigFromDb(config.getActivityInfo());
    }

    public String hotCode(String param) {
        return "";
    }

}
